欢迎访问 生活随笔!

生活随笔

当前位置: 首页 >

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

发布时间:2023/12/20 56 豆豆
生活随笔 收集整理的这篇文章主要介绍了 挑战:工资计算器读写数据文件 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

重新实现上一个挑战中的计算器,可以支持从配置文件中读取社保的税率,并读取员工工资数据 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()@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')# 创建一个全局参数类对象供后续使用 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 configdef _get_config(self, key):"""内部函数,用来获得配置项的值"""try:return self.config[key]except KeyError:print('Config Error')exit()@propertydef social_insurance_baseline_low(self):"""获取社保基数下限"""return self._get_config('JiShuL')@propertydef social_insurance_baseline_high(self):"""获取社保基数上限"""return self._get_config('JiShuH')@propertydef 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 userlistdef get_userlist(self):"""获取用户数据列表"""# 直接返回属性 userlist 列表对象return self.userlistclass IncomeTaxCalculator(object):"""税后工资计算类"""def __init__(self, userdata):# 初始化时接收一个 UserData 对象self.userdata = userdata@classmethoddef calc_social_insurance_money(cls, income):"""计算社保金额"""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):"""计算税后工资"""# 计算社保金额social_insurance_money = cls.calc_social_insurance_money(income)# 计算应纳税额real_income = income - social_insurance_moneytaxable_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_subtractorreturn '{:.2f}'.format(tax), '{:.2f}'.format(real_income - tax)# 如果没有落入任何区间,则返回 0return '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 resultdef 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@classmethoddef 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@classmethoddef 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

    总结

    以上是生活随笔为你收集整理的挑战:工资计算器读写数据文件的全部内容,希望文章能够帮你解决所遇到的问题。

    如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。