Skip to content

Commit b3fb9dd

Browse files
committed
update 03-challenge
1 parent 045fe85 commit b3fb9dd

1 file changed

Lines changed: 84 additions & 8 deletions

File tree

03-income-tax-calculator-use-config-file/challenge3.py

Lines changed: 84 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,18 @@
33
import csv
44
from collections import namedtuple
55

6+
# 使用 nametuple 的方式来存储个税计算表
7+
# 优势是避免了使用索引来获取个税阶梯和税率造成代码难以维护的状态
68
IncomeTaxQuickLookupItem = namedtuple(
79
'IncomeTaxQuickLookupItem',
810
['start_point', 'tax_rate', 'quick_subtractor']
911
)
1012

13+
# 个税起征点 3500
1114
INCOME_TAX_START_POINT = 3500
1215

16+
# 个税计算表,列表存储,每一行都是一个 namedtuple
17+
# 每个 namedtuple 包含该计算阶梯的起始薪资、税率、速算扣除数
1318
INCOME_TAX_QUICK_LOOKUP_TABLE = [
1419
IncomeTaxQuickLookupItem(80000, 0.45, 13505),
1520
IncomeTaxQuickLookupItem(55000, 0.35, 5505),
@@ -20,69 +25,94 @@
2025
IncomeTaxQuickLookupItem(0, 0.03, 0)
2126
]
2227

23-
28+
# 命令行参数处理类
2429
class 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 的代码定义中需要用到这个对象
5064
args = Args()
5165

52-
66+
# 配置文件读取处理类
5367
class 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+
# 的代码定义中需要用到这个对象
98130
config = Config()
99131

100-
132+
# 用户工资文件处理类
101133
class 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+
# 税后工资计算类
124169
class 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

168242
if __name__ == '__main__':
243+
# 创建 UserData 对象并使用该对象初始化工资计算器
169244
calculator = IncomeTaxCalculator(UserData())
245+
# 调用工资计算器对象 calculator 中的 export 方法导出结果数据到文件
170246
calculator.export()

0 commit comments

Comments
 (0)