重新實現上一個挑戰中的計算器,可以支持從配置文件中讀取社保的稅率,並讀取員工工資數據 CSV 文件,同時將輸出信息寫入員工工資單 CSV 文件中。
計算器執行中包含下面的三個參數:
-c 社保比例配置文件:由於各地的社保比例稍有不同,需要爲每個城市提供一個單獨的社保比例的配置,本挑戰假定不考慮各地社保差異,僅提供一份通用配置。
-d 員工工資數據文件(CSV 格式): 指定員工工資數據文件,文件中包含兩列內容,分別爲員工工號和工資金額。
-o 員工工資單數據文件(CSV 格式): 輸出內容,將員工繳納的社保、稅前、稅後工資等詳細信息輸出到文件中。
1. 配置文件說明
社保比例配置文件格式示例如下(等號兩邊均有空格):
JiShuL = 2193.00
JiShuH = 16446.00
YangLao = 0.08
YiLiao = 0.02
ShiYe = 0.005
GongShang = 0
ShengYu = 0
GongJiJin = 0.06
將以上數據寫入 /home/shiyanlou/test.cfg 文件中。
配置文件中,各類保險以其漢語拼音命名(養老保險 → YangLao,公積金 → GongJiJin 等)。特別需要注意的是:
JiShuL 爲社保繳費基數的下限,即工資低於 JiShuL 的值的時候,需要按照 JiShuL 的數值乘以繳費比例來繳納社保。
JiShuH 爲社保繳費基數的上限,即工資高於 JiShuH 的值的時候,需要按照 JiShuH 的數值乘以繳費比例繳納社保。
當工資在 JiShuL 和 JiShuH 之間的時候,按照你實際的工資金額乘以繳費比例計算社保費用。
例如:當工資爲 20000 時,因爲社保基數爲 2193(JiShuL)~ 16446(JiShuH),所以是按照社保基數上限 16446(而不是用 20000) 去乘以社保的繳費比例計算實際繳納的社保數額。
- 員工工資數據文件說明
員工工資數據文件,即本實驗中輸入的數據文件。每位員工工資數據單獨佔一行,文件格式爲 工號,稅前工資,舉例如下:
101,5000
203,6500
309,15000
將以上數據寫入 /home/shiyanlou/user.csv 文件中。
- 員工工資單數據文件說明
員工工資單數據文件,即本實驗需要輸出得到的數據文件。同樣,輸出的員工工資單數據文件中,每行各項數據用逗號隔開,各項數據爲 工號,稅前工資,社保金額,個稅金額,稅後工資,舉例如下:
101,5000,825.00,0.00,4175.00
203,6500,1072.50,12.82,5414.68
309,15000,2475.00,542.50,11982.50
需要特別注意的是:
上面只是示例輸出(3 行數據),測試時候用的數據文件可能有更多行,輸出的文件行數要與測試文件行數相同,但不需要保持相同的順序。
程序的執行過程如下,配置文件 test.cfg 和輸入的員工數據文件 user.csv 需要自己創建並填入數據(可參考上述內容示例)。文件可以放在任何位置,只要參數中指定文件的路徑就可以了,示例如下:
$ ./calculator.py -c /home/shiyanlou/test.cfg -d /home/shiyanlou/user.csv -o /tmp/gongzi.csv
執行成功不需要輸出信息到屏幕,執行失敗或有異常出現則將錯誤信息輸出到屏幕。
import sys
import csv
from collections import namedtuple
# 稅率表條目類,該類由 namedtuple 動態創建,代表一個命名元組
IncomeTaxQuickLookupItem = namedtuple(
'IncomeTaxQuickLookupItem',
['start_point', 'tax_rate', 'quick_subtractor']
)
# 起徵點常量
INCOME_TAX_START_POINT = 5000
# 稅率表,裏面的元素類型爲前面創建的 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):
"""
命令行參數處理類
"""
def __init__(self):
# 保存命令行參數列表
self.args = sys.argv[1:]
def _value_after_option(self, option):
"""
內部函數,用來獲取跟在選項後面的值
"""
try:
# 獲得選項位置
index = self.args.index(option)
# 下一位置即爲選項值
return self.args[index + 1]
except (ValueError, IndexError):
print('Parameter Error')
exit()
@property
def config_path(self):
"""
配置文件路徑
"""
return self._value_after_option('-c')
@property
def userdata_path(self):
"""
用戶工資文件路徑
"""
return self._value_after_option('-d')
@property
def export_path(self):
"""
稅後工資文件路徑
"""
return self._value_after_option('-o')
# 創建一個全局參數類對象供後續使用
args = Args()
class Config(object):
"""
配置文件處理類
"""
def __init__(self):
# 讀取配置文件
self.config = self._read_config()
def _read_config(self):
"""
內部函數,用來讀取配置文件中的配置項
"""
config = {}
with open(args.config_path) as f:
# 依次讀取配置文件裏的每一行並解析得到配置項名稱和值
for line in f.readlines():
key, value = line.strip().split('=')
try:
# 去掉前後可能出現的空格
config[key.strip()] = float(value.strip())
except ValueError:
print('Parameter Error')
exit()
return config
def _get_config(self, key):
"""
內部函數,用來獲得配置項的值
"""
try:
return self.config[key]
except KeyError:
print('Config Error')
exit()
@property
def social_insurance_baseline_low(self):
"""
獲取社保基數下限
"""
return self._get_config('JiShuL')
@property
def social_insurance_baseline_high(self):
"""
獲取社保基數上限
"""
return self._get_config('JiShuH')
@property
def social_insurance_total_rate(self):
"""
獲取社保總費率
"""
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')
])
# 創建一個全局的配置文件處理對象供後續使用
config = Config()
class UserData(object):
"""
用戶工資文件處理類
"""
def __init__(self):
# 讀取用戶工資文件
self.userlist = self._read_users_data()
def _read_users_data(self):
"""
內部函數,用來讀取用戶工資文件
"""
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 userlist
def get_userlist(self):
"""
獲取用戶數據列表
"""
# 直接返回屬性 userlist 列表對象
return self.userlist
class IncomeTaxCalculator(object):
"""
稅後工資計算類
"""
def __init__(self, userdata):
# 初始化時接收一個 UserData 對象
self.userdata = userdata
@classmethod
def calc_social_insurance_money(cls, income):
"""
計算社保金額
"""
if income < config.social_insurance_baseline_low:
return config.social_insurance_baseline_low * \
config.social_insurance_total_rate
elif income > config.social_insurance_baseline_high:
return config.social_insurance_baseline_high * \
config.social_insurance_total_rate
else:
return income * config.social_insurance_total_rate
@classmethod
def calc_income_tax_and_remain(cls, income):
"""
計算稅後工資
"""
# 計算社保金額
social_insurance_money = cls.calc_social_insurance_money(income)
# 計算應納稅額
real_income = income - social_insurance_money
taxable_part = real_income - INCOME_TAX_START_POINT
# 從高到低判斷落入的稅率區間,如果找到則用該區間的參數計算納稅額並返回結果
for item in INCOME_TAX_QUICK_LOOKUP_TABLE:
if taxable_part > item.start_point:
tax = taxable_part * item.tax_rate - item.quick_subtractor
return '{:.2f}'.format(tax), '{:.2f}'.format(real_income - tax)
# 如果沒有落入任何區間,則返回 0
return '0.00', '{:.2f}'.format(real_income)
def calc_for_all_userdata(self):
"""
計算所有用戶的稅後工資
"""
result = []
# 循環計算每一個用戶的稅後工資,並將結果彙總到結果集中
for employee_id, income in self.userdata.get_userlist():
# 計算社保金額
social_insurance_money = '{:.2f}'.format(
self.calc_social_insurance_money(income))
# 計算稅後工資
tax, remain = self.calc_income_tax_and_remain(income)
# 添加到結果集
result.append(
[employee_id, income, social_insurance_money, tax, remain])
return result
def export(self):
"""
導出所有用戶的稅後工資到文件
"""
# 計算所有用戶的稅後工資
result = self.calc_for_all_userdata()
with open(args.export_path, 'w', newline='') as f:
# 創建 csv 文件寫入對象
writer = csv.writer(f)
# 寫入多行數據
writer.writerows(result)
if __name__ == '__main__':
# 創建稅後工資計算器
calculator = IncomeTaxCalculator(UserData())
# 調用 export 方法導出稅後工資到文件
calculator.export()
需要注意社保基數的處理,比如 20000 元工資高於社保基數的上限 JiShuH 的值,就應該用 JiShuH 這個值去乘以比例計算需要繳納的社保金額。
可以實現一個配置類 Config,來獲取並存儲配置文件中的信息,Config 類 def __init__(self, configfile) 中定義一個字典 self._config = {} 來存儲每個配置項和值,從文件中讀取的時候需要注意使用 strip() 去掉空格,並可以使用字符串的 split('=') 將配置項和值切分開。從 Config 對象中獲得配置信息的方法可以定義爲 def get_config(self),使用類似 config.get_config('JiShuH')。
可以實現一個員工數據類 UserData,來獲取並存儲員工數據,同樣 def __init__(self, userdatafile) 中定義一個字典 self.userdata = {} 存儲文件中讀取的用戶 ID及工資,並實現相應的金額計算的方法def calculator(self) 及輸出到文件中的方法 def dumptofile(self, outputfile)。
需要在上述類中實現文件讀取和寫入等操作,寫入的格式需要保證符合上述描述內容。
處理命令行參數的方式:
首先使用 args = sys.argv[1:] 獲得所有的命令行參數列表,即包括 -c test.cfg -d user.csv -o gongzi.csv 這些內容。
使用 index = args.index('-c') 獲得 -c 參數的索引,那麼配置文件的路徑就是 -c 後的參數即 configfile = args[index+1],同樣,其他的 -d 和 -o 參數也用這種方法獲得。
在 Windows 系統中使用 Python 代碼寫入 csv 文件會出現空行,加個參數 newline='' 即可解決:
>>> with open('xxx.csv', 'w', newline='') as f:
... csv.writer(f).writerows(data)
...
深入理解python @classmethod
被@classmethod裝飾的方法
1. 強制帶個參數,cls,cls代表這個類本身
2. 不用實例化,和靜態方法一樣,直接 類().方法() 即可調用
3. cls是個位置參數,可隨意換成其他詞,如this
如想獲取類屬性x的值,可直接cls.x,等價於A.x
class A():
x = 1
@classmethod
def B(cls):
print(cls.x)
>> A.B()
1
已知cls代表類本身,那麼cls(123),就等價於A(123),調用init初始化,實例化爲x
cls(123) 等價於 x = A(123)
class A():
def __init__(self, q):
self.q = q
@classmethod
def B(cls):
return cls(123)
>> x = A.B()
>> print(x.q)
123
運行邏輯: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內置的@property裝飾器就是負責把一個方法變成屬性調用:
https://www.cnblogs.com/phpper/p/10618775.html