33import csv
44from collections import namedtuple
55
6+ # 使用 nametuple 的方式来存储个税计算表
7+ # 优势是避免了使用索引来获取个税阶梯和税率造成代码难以维护的状态
68IncomeTaxQuickLookupItem = namedtuple (
79 'IncomeTaxQuickLookupItem' ,
810 ['start_point' , 'tax_rate' , 'quick_subtractor' ]
911)
1012
13+ # 个税起征点 3500
1114INCOME_TAX_START_POINT = 3500
1215
16+ # 个税计算表,列表存储,每一行都是一个 namedtuple
17+ # 每个 namedtuple 包含该计算阶梯的起始薪资、税率、速算扣除数
1318INCOME_TAX_QUICK_LOOKUP_TABLE = [
1419 IncomeTaxQuickLookupItem (80000 , 0.45 , 13505 ),
1520 IncomeTaxQuickLookupItem (55000 , 0.35 , 5505 ),
2025 IncomeTaxQuickLookupItem (0 , 0.03 , 0 )
2126]
2227
23-
28+ # 命令行参数处理类
2429class Args (object ):
2530
31+ # 初始化的时候读取命令行中输入的所有参数到 self.args 列表
2632 def __init__ (self ):
2733 self .args = sys .argv [1 :]
2834
35+ # 内部函数,用来提取参数 -c/-d/-o 后面的值
2936 def _value_after_option (self , option ):
3037 try :
38+ # 首先获得参数 -c/-d/-o 所在位置在列表 sys.args 中的索引值
3139 index = self .args .index (option )
40+ # 获取 -c/-d/-o 所在位置的下一个位置的字符串就是对应的值
3241 return self .args [index + 1 ]
3342 except (ValueError , IndexError ):
43+ # 如果获取出错,则打印错误信息并退出
3444 print ('Parameter Error' )
3545 exit ()
3646
47+ # 获取 -c 参数对应的值,即配置文件的路径
3748 @property
3849 def config_path (self ):
3950 return self ._value_after_option ('-c' )
4051
52+ # 获取 -d 参数对应的值,即用户工资数据文件的路径
4153 @property
4254 def userdata_path (self ):
4355 return self ._value_after_option ('-d' )
4456
57+ # 获取 -o 参数对应的值,即输出的用户工资单文件的路径
4558 @property
4659 def export_path (self ):
4760 return self ._value_after_option ('-o' )
4861
49-
62+ # 创建命令行参数处理的对象 args
63+ # 在此处创建的原因是后续的 class Config 的代码定义中需要用到这个对象
5064args = Args ()
5165
52-
66+ # 配置文件读取处理类
5367class Config (object ):
5468
69+ # 初始化的时候调用内部接口 self._read_config() 读取配置文件中的所有内容
70+ # 读取的配置项和值都存入字典 self.config 中
5571 def __init__ (self ):
5672 self .config = self ._read_config ()
5773
74+ # 内部函数,用来读取配置文件中的配置项
5875 def _read_config (self ):
76+ # 从 args 对象中获得配置文件路径
5977 config_path = args .config_path
78+ # 初始化存储配置项和值的字典
6079 config = {}
80+ # 打开配置文件读取数据
6181 with open (config_path ) as f :
82+ # 读取每一行数据
6283 for line in f .readlines ():
84+ # 使用 = 分割每一行的内容,注意需要去掉每行两边的空格
6385 key , value = line .strip ().split (' = ' )
6486 try :
65- config [key ] = float (value )
87+ # 将配置项和对应的值存入到字典中,注意需要去除字符串两边的空格
88+ config [key .strip ()] = float (value .strip ())
6689 except ValueError :
90+ # 如果配置值不能转成 float,则报错退出
6791 print ('Parameter Error' )
6892 exit ()
93+ # 返回存储配置项和值的字典
6994 return config
7095
96+ # 内部函数,用来使用配置项获得配置的值
7197 def _get_config (self , key ):
7298 try :
7399 return self .config [key ]
74100 except KeyError :
101+ # 如果配置项不存在则打印错误并退出
75102 print ('Config Error' )
76103 exit ()
77104
105+ # 获取社保基数下限
78106 @property
79107 def social_insurance_baseline_low (self ):
80108 return self ._get_config ('JiShuL' )
81109
110+ # 获取社保基数上限
82111 @property
83112 def social_insurance_baseline_high (self ):
84113 return self ._get_config ('JiShuH' )
85114
115+ # 获取社保总费率,分别获取每一项的费率后再使用 sum 计算列表中每一项的和
86116 @property
87117 def social_insurance_total_rate (self ):
88118 return sum ([
@@ -94,77 +124,123 @@ def social_insurance_total_rate(self):
94124 self ._get_config ('GongJiJin' )
95125 ])
96126
97-
127+ # 创建配置文件处理的对象 config
128+ # 在此处创建的原因是后续的 class IncomeTaxCalculator
129+ # 的代码定义中需要用到这个对象
98130config = Config ()
99131
100-
132+ # 用户工资文件处理类
101133class UserData (object ):
102134
135+ # 初始化过程,读取用户工资文件并将数据存入到 userdata 列表中
103136 def __init__ (self ):
104137 self .userdata = self ._read_users_data ()
105138
139+ # 内部函数,用来读取用户工资文件
106140 def _read_users_data (self ):
141+ # 从 args 中获取用户工资文件路径
107142 userdata_path = args .userdata_path
143+ # 初始化存储的列表
108144 userdata = []
145+ # 打开用户工资文件
109146 with open (userdata_path ) as f :
147+ # 读取用户工资文件中的每一行内容类似:101,3500
110148 for line in f .readlines ():
149+ # 使用逗号分割每一行的字符串,得到工号和工资
111150 employee_id , income_string = line .strip ().split (',' )
112151 try :
152+ # 将工资字符串转为整数
113153 income = int (income_string )
114154 except ValueError :
155+ # 如果工资无法转为整数则报错退出
115156 print ('Parameter Error' )
116157 exit ()
158+ # 将每一行的数据转成二元组后添加到 userdata 列表
117159 userdata .append ((employee_id , income ))
160+ # 返回用户工资数据列表
118161 return userdata
119162
163+ # 添加 __iter__ 将 UserData 对象成为可迭代对象,
164+ # 即可以用 for 循环获取中间的 userdata 列表里的数据
120165 def __iter__ (self ):
121166 return iter (self .userdata )
122167
123-
168+ # 税后工资计算类
124169class IncomeTaxCalculator (object ):
125170
171+ # 初始化的时候传入 UserData 对象,传进来后赋值给 self.userdata
126172 def __init__ (self , userdata ):
127173 self .userdata = userdata
128174
175+ # 静态成员方法:完全可以单独实现的方法,仅仅是因为与工资计算类有一些关联才放进入类中
176+ # 计算需要缴纳的社保金额,传入的参数为工资金额
129177 @staticmethod
130178 def calc_social_insurance_money (income ):
179+ # 如果工资小于社保基数下限,则用社保基数下限计算社保
131180 if income < config .social_insurance_baseline_low :
132181 return config .social_insurance_baseline_low * \
133182 config .social_insurance_total_rate
183+ # 如果工资大于社保基数上限,则用社保基数上限计算社保
134184 if income > config .social_insurance_baseline_high :
135185 return config .social_insurance_baseline_high * \
136186 config .social_insurance_total_rate
187+ # 其他情况,则用工资计算社保
137188 return income * config .social_insurance_total_rate
138189
190+ # 类方法:不需要实例化类也能够调用的方法,需要传入代表类的 cls
191+ # 计算个税和税后工资,传入的参数为工资金额
139192 @classmethod
140193 def calc_income_tax_and_remain (cls , income ):
194+ # 计算社保金额
141195 social_insurance_money = cls .calc_social_insurance_money (income )
196+ # 获得应纳税所得额
142197 real_income = income - social_insurance_money
143198 taxable_part = real_income - INCOME_TAX_START_POINT
199+ # 如果应纳税所得额小于0,则交税0,税后工资为工资减去社保
144200 if taxable_part <= 0 :
145201 return '0.00' , '{:.2f}' .format (real_income )
202+ # 使用个税计算表计算个税
146203 for item in INCOME_TAX_QUICK_LOOKUP_TABLE :
204+ # 循环遍历个税计算表,直到找到应纳税所得额所在的区间
147205 if taxable_part > item .start_point :
206+ # 使用公式计算个税
148207 tax = taxable_part * item .tax_rate - item .quick_subtractor
208+ # 返回个税及税后工资,注意需要保留两位小数
149209 return '{:.2f}' .format (tax ), '{:.2f}' .format (real_income - tax )
150210
211+ # 计算所有用户工资
212+ # 直接使用 self.userdata 对象计算其中的每一个数据
151213 def calc_for_all_userdata (self ):
214+ # 初始化返回结果
152215 result = []
216+ # 循环获取 userdata 中的用户工号和税前工资
153217 for employee_id , income in self .userdata :
218+ # 初始化返回的数据结果,包含工号和税前工资
154219 data = [employee_id , income ]
220+ # 计算需要缴纳的社保,注意需要保留两位小数
155221 social_insurance_money = '{:.2f}' .format (self .calc_social_insurance_money (income ))
222+ # 计算个税及税后工资,注意需要保留两位小数
156223 tax , remain = self .calc_income_tax_and_remain (income )
224+ # 将社保、个税及税后工资补充到返回的数据列表中
157225 data += [social_insurance_money , tax , remain ]
158226 result .append (data )
227+ # 返回所有用户的工资数据
159228 return result
160229
161- def export (self , default = 'csv' ):
230+ # 导出到工资数据文件,传入的参数用来指定导出的文件类型,此处用来未来扩展
231+ def export (self , file_type = 'csv' ):
232+ # 计算并获得所有用户的工资数据
162233 result = self .calc_for_all_userdata ()
234+ # 打开导出的文件,并写入
163235 with open (args .export_path , 'w' , newline = '' ) as f :
236+ # 使用 csv 模块创建 writer 对象
164237 writer = csv .writer (f )
238+ # 向文件中以 csv 格式写入列表
165239 writer .writerows (result )
166240
167241
168242if __name__ == '__main__' :
243+ # 创建 UserData 对象并使用该对象初始化工资计算器
169244 calculator = IncomeTaxCalculator (UserData ())
245+ # 调用工资计算器对象 calculator 中的 export 方法导出结果数据到文件
170246 calculator .export ()
0 commit comments