挑战:工资计算器读写数据文件
重新實(shí)現(xiàn)上一個挑戰(zhàn)中的計(jì)算器,可以支持從配置文件中讀取社保的稅率,并讀取員工工資數(shù)據(jù) CSV 文件,同時將輸出信息寫入員工工資單 CSV 文件中。
計(jì)算器執(zhí)行中包含下面的三個參數(shù):
-c 社保比例配置文件:由于各地的社保比例稍有不同,需要為每個城市提供一個單獨(dú)的社保比例的配置,本挑戰(zhàn)假定不考慮各地社保差異,僅提供一份通用配置。 -d 員工工資數(shù)據(jù)文件(CSV 格式): 指定員工工資數(shù)據(jù)文件,文件中包含兩列內(nèi)容,分別為員工工號和工資金額。 -o 員工工資單數(shù)據(jù)文件(CSV 格式): 輸出內(nèi)容,將員工繳納的社保、稅前、稅后工資等詳細(xì)信息輸出到文件中。 1. 配置文件說明 社保比例配置文件格式示例如下(等號兩邊均有空格): JiShuL = 2193.00 JiShuH = 16446.00 YangLao = 0.08 YiLiao = 0.02 ShiYe = 0.005 GongShang = 0 ShengYu = 0 GongJiJin = 0.06將以上數(shù)據(jù)寫入 /home/shiyanlou/test.cfg 文件中。
配置文件中,各類保險(xiǎn)以其漢語拼音命名(養(yǎng)老保險(xiǎn) → YangLao,公積金 → GongJiJin 等)。特別需要注意的是:
JiShuL 為社保繳費(fèi)基數(shù)的下限,即工資低于 JiShuL 的值的時候,需要按照 JiShuL 的數(shù)值乘以繳費(fèi)比例來繳納社保。
JiShuH 為社保繳費(fèi)基數(shù)的上限,即工資高于 JiShuH 的值的時候,需要按照 JiShuH 的數(shù)值乘以繳費(fèi)比例繳納社保。
當(dāng)工資在 JiShuL 和 JiShuH 之間的時候,按照你實(shí)際的工資金額乘以繳費(fèi)比例計(jì)算社保費(fèi)用。
例如:當(dāng)工資為 20000 時,因?yàn)樯绫;鶖?shù)為 2193(JiShuL)~ 16446(JiShuH),所以是按照社保基數(shù)上限 16446(而不是用 20000) 去乘以社保的繳費(fèi)比例計(jì)算實(shí)際繳納的社保數(shù)額。
員工工資數(shù)據(jù)文件,即本實(shí)驗(yàn)中輸入的數(shù)據(jù)文件。每位員工工資數(shù)據(jù)單獨(dú)占一行,文件格式為 工號,稅前工資,舉例如下:
將以上數(shù)據(jù)寫入 /home/shiyanlou/user.csv 文件中。
員工工資單數(shù)據(jù)文件,即本實(shí)驗(yàn)需要輸出得到的數(shù)據(jù)文件。同樣,輸出的員工工資單數(shù)據(jù)文件中,每行各項(xiàng)數(shù)據(jù)用逗號隔開,各項(xiàng)數(shù)據(jù)為 工號,稅前工資,社保金額,個稅金額,稅后工資,舉例如下:
需要特別注意的是:
上面只是示例輸出(3 行數(shù)據(jù)),測試時候用的數(shù)據(jù)文件可能有更多行,輸出的文件行數(shù)要與測試文件行數(shù)相同,但不需要保持相同的順序。
程序的執(zhí)行過程如下,配置文件 test.cfg 和輸入的員工數(shù)據(jù)文件 user.csv 需要自己創(chuàng)建并填入數(shù)據(jù)(可參考上述內(nèi)容示例)。文件可以放在任何位置,只要參數(shù)中指定文件的路徑就可以了,示例如下:
$ ./calculator.py -c /home/shiyanlou/test.cfg -d /home/shiyanlou/user.csv -o /tmp/gongzi.csv執(zhí)行成功不需要輸出信息到屏幕,執(zhí)行失敗或有異常出現(xiàn)則將錯誤信息輸出到屏幕。
import sys import csv from collections import namedtuple# 稅率表?xiàng)l目類,該類由 namedtuple 動態(tài)創(chuàng)建,代表一個命名元組 IncomeTaxQuickLookupItem = namedtuple('IncomeTaxQuickLookupItem',['start_point', 'tax_rate', 'quick_subtractor'] )# 起征點(diǎn)常量 INCOME_TAX_START_POINT = 5000# 稅率表,里面的元素類型為前面創(chuàng)建的 IncomeTaxQuickLookupItem INCOME_TAX_QUICK_LOOKUP_TABLE = [IncomeTaxQuickLookupItem(80000, 0.45, 15160),IncomeTaxQuickLookupItem(55000, 0.35, 7160),IncomeTaxQuickLookupItem(35000, 0.30, 4410),IncomeTaxQuickLookupItem(25000, 0.25, 2660),IncomeTaxQuickLookupItem(12000, 0.2, 1410),IncomeTaxQuickLookupItem(3000, 0.1, 210),IncomeTaxQuickLookupItem(0, 0.03, 0) ]class Args(object):"""命令行參數(shù)處理類"""def __init__(self):# 保存命令行參數(shù)列表self.args = sys.argv[1:]def _value_after_option(self, option):"""內(nèi)部函數(shù),用來獲取跟在選項(xiàng)后面的值"""try:# 獲得選項(xiàng)位置index = self.args.index(option)# 下一位置即為選項(xiàng)值return self.args[index + 1]except (ValueError, IndexError):print('Parameter Error')exit()@propertydef config_path(self):"""配置文件路徑"""return self._value_after_option('-c')@propertydef userdata_path(self):"""用戶工資文件路徑"""return self._value_after_option('-d')@propertydef export_path(self):"""稅后工資文件路徑"""return self._value_after_option('-o')# 創(chuàng)建一個全局參數(shù)類對象供后續(xù)使用 args = Args()class Config(object):"""配置文件處理類"""def __init__(self):# 讀取配置文件self.config = self._read_config()def _read_config(self):"""內(nèi)部函數(shù),用來讀取配置文件中的配置項(xiàng)"""config = {}with open(args.config_path) as f:# 依次讀取配置文件里的每一行并解析得到配置項(xiàng)名稱和值for line in f.readlines():key, value = line.strip().split('=')try:# 去掉前后可能出現(xiàn)的空格config[key.strip()] = float(value.strip())except ValueError:print('Parameter Error')exit()return configdef _get_config(self, key):"""內(nèi)部函數(shù),用來獲得配置項(xiàng)的值"""try:return self.config[key]except KeyError:print('Config Error')exit()@propertydef social_insurance_baseline_low(self):"""獲取社保基數(shù)下限"""return self._get_config('JiShuL')@propertydef social_insurance_baseline_high(self):"""獲取社保基數(shù)上限"""return self._get_config('JiShuH')@propertydef social_insurance_total_rate(self):"""獲取社保總費(fèi)率"""return sum([self._get_config('YangLao'),self._get_config('YiLiao'),self._get_config('ShiYe'),self._get_config('GongShang'),self._get_config('ShengYu'),self._get_config('GongJiJin')])# 創(chuàng)建一個全局的配置文件處理對象供后續(xù)使用 config = Config()class UserData(object):"""用戶工資文件處理類"""def __init__(self):# 讀取用戶工資文件self.userlist = self._read_users_data()def _read_users_data(self):"""內(nèi)部函數(shù),用來讀取用戶工資文件"""userlist = []with open(args.userdata_path) as f:# 依次讀取用戶工資文件中的每一行并解析得到用戶 ID 和工資for line in f.readlines():employee_id, income_string = line.strip().split(',')try:income = int(income_string)except ValueError:print('Parameter Error')exit()userlist.append((employee_id, income))return userlistdef get_userlist(self):"""獲取用戶數(shù)據(jù)列表"""# 直接返回屬性 userlist 列表對象return self.userlistclass IncomeTaxCalculator(object):"""稅后工資計(jì)算類"""def __init__(self, userdata):# 初始化時接收一個 UserData 對象self.userdata = userdata@classmethoddef calc_social_insurance_money(cls, income):"""計(jì)算社保金額"""if income < config.social_insurance_baseline_low:return config.social_insurance_baseline_low * \config.social_insurance_total_rateelif income > config.social_insurance_baseline_high:return config.social_insurance_baseline_high * \config.social_insurance_total_rateelse:return income * config.social_insurance_total_rate@classmethoddef calc_income_tax_and_remain(cls, income):"""計(jì)算稅后工資"""# 計(jì)算社保金額social_insurance_money = cls.calc_social_insurance_money(income)# 計(jì)算應(yīng)納稅額real_income = income - social_insurance_moneytaxable_part = real_income - INCOME_TAX_START_POINT# 從高到低判斷落入的稅率區(qū)間,如果找到則用該區(qū)間的參數(shù)計(jì)算納稅額并返回結(jié)果for item in INCOME_TAX_QUICK_LOOKUP_TABLE:if taxable_part > item.start_point:tax = taxable_part * item.tax_rate - item.quick_subtractorreturn '{:.2f}'.format(tax), '{:.2f}'.format(real_income - tax)# 如果沒有落入任何區(qū)間,則返回 0return '0.00', '{:.2f}'.format(real_income)def calc_for_all_userdata(self):"""計(jì)算所有用戶的稅后工資"""result = []# 循環(huán)計(jì)算每一個用戶的稅后工資,并將結(jié)果匯總到結(jié)果集中for employee_id, income in self.userdata.get_userlist():# 計(jì)算社保金額social_insurance_money = '{:.2f}'.format(self.calc_social_insurance_money(income))# 計(jì)算稅后工資tax, remain = self.calc_income_tax_and_remain(income)# 添加到結(jié)果集result.append([employee_id, income, social_insurance_money, tax, remain])return resultdef export(self):"""導(dǎo)出所有用戶的稅后工資到文件"""# 計(jì)算所有用戶的稅后工資result = self.calc_for_all_userdata()with open(args.export_path, 'w', newline='') as f:# 創(chuàng)建 csv 文件寫入對象writer = csv.writer(f)# 寫入多行數(shù)據(jù)writer.writerows(result)if __name__ == '__main__':# 創(chuàng)建稅后工資計(jì)算器calculator = IncomeTaxCalculator(UserData())# 調(diào)用 export 方法導(dǎo)出稅后工資到文件calculator.export() 需要注意社保基數(shù)的處理,比如 20000 元工資高于社保基數(shù)的上限 JiShuH 的值,就應(yīng)該用 JiShuH 這個值去乘以比例計(jì)算需要繳納的社保金額。可以實(shí)現(xiàn)一個配置類 Config,來獲取并存儲配置文件中的信息,Config 類 def __init__(self, configfile) 中定義一個字典 self._config = {} 來存儲每個配置項(xiàng)和值,從文件中讀取的時候需要注意使用 strip() 去掉空格,并可以使用字符串的 split('=') 將配置項(xiàng)和值切分開。從 Config 對象中獲得配置信息的方法可以定義為 def get_config(self),使用類似 config.get_config('JiShuH')。可以實(shí)現(xiàn)一個員工數(shù)據(jù)類 UserData,來獲取并存儲員工數(shù)據(jù),同樣 def __init__(self, userdatafile) 中定義一個字典 self.userdata = {} 存儲文件中讀取的用戶 ID及工資,并實(shí)現(xiàn)相應(yīng)的金額計(jì)算的方法def calculator(self) 及輸出到文件中的方法 def dumptofile(self, outputfile)。需要在上述類中實(shí)現(xiàn)文件讀取和寫入等操作,寫入的格式需要保證符合上述描述內(nèi)容。處理命令行參數(shù)的方式:首先使用 args = sys.argv[1:] 獲得所有的命令行參數(shù)列表,即包括 -c test.cfg -d user.csv -o gongzi.csv 這些內(nèi)容。 使用 index = args.index('-c') 獲得 -c 參數(shù)的索引,那么配置文件的路徑就是 -c 后的參數(shù)即 configfile = args[index+1],同樣,其他的 -d 和 -o 參數(shù)也用這種方法獲得。 在 Windows 系統(tǒng)中使用 Python 代碼寫入 csv 文件會出現(xiàn)空行,加個參數(shù) newline='' 即可解決:>>> with open('xxx.csv', 'w', newline='') as f: ... csv.writer(f).writerows(data) ... 深入理解python @classmethod被@classmethod裝飾的方法 1. 強(qiáng)制帶個參數(shù),cls,cls代表這個類本身 2. 不用實(shí)例化,和靜態(tài)方法一樣,直接 類().方法() 即可調(diào)用 3. cls是個位置參數(shù),可隨意換成其他詞,如this如想獲取類屬性x的值,可直接cls.x,等價(jià)于A.x class A():x = 1@classmethoddef B(cls):print(cls.x)>> A.B() 1已知cls代表類本身,那么cls(123),就等價(jià)于A(123),調(diào)用init初始化,實(shí)例化為x cls(123) 等價(jià)于 x = A(123)class A():def __init__(self, q):self.q = q@classmethoddef B(cls):return cls(123)>> x = A.B() >> print(x.q) 123 運(yùn)行邏輯:A() - B() - cls(123) - x = A(123)也許:https://blog.csdn.net/qq_39698985/article/details/106729710?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.nonecase
使用Python內(nèi)置的@property裝飾器就是負(fù)責(zé)把一個方法變成屬性調(diào)用:
https://www.cnblogs.com/phpper/p/10618775.html
總結(jié)
以上是生活随笔為你收集整理的挑战:工资计算器读写数据文件的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 创建样式和样式表
- 下一篇: VirtualBox的Linux虚拟机访