挑战:工资计算器读写数据文件

    技术2025-10-16  19

    重新实现上一个挑战中的计算器,可以支持从配置文件中读取社保的税率,并读取员工工资数据 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

    Processed: 0.015, SQL: 9