介绍
重新实现上一个挑战中的计算器,可以支持从配置文件中读取社保的税率,并读取员工工资数据 CSV 文件,同时将输出信息写入员工工资单 CSV 文件中。
计算器执行中包含下面的三个参数:
-c
配置文件:由于各地的社保比例稍有不同,我们为每个城市提供一个社保比例的配置文件。-d
员工工资数据文件(CSV 格式): 指定员工工资数据文件,文件中包含两列内容,分别为员工工号和工资金额。-o
员工工资单数据文件(CSV 格式): 输出内容,将员工缴纳的社保、税前、税后工资等详细信息输出到文件中。
1. 配置文件说明
配置文件格式示例如下:
配置文件中,各类保险以其汉语拼音命名(养老保险 → YangLao,公积金 → GongJiJin 等)。特别需要注意的是:
JiShuL
为社保缴费基数的下限,即工资低于JiShuL
的值的时候,需要按照JiShuL
的数值乘以缴费比例来缴纳社保。JiShuH
为社保缴费基数的上限,即工资高于JiShuH
的值的时候,需要按照JiShuH
的数值乘以缴费比例缴纳社保。- 当工资在
JiShuL
和JiShuH
之间的时候,按照你实际的工资金额乘以缴费比例计算社保费用。
例如:当工资为 20000 时,因为社保基数为 2193(JiShuL
) ~ 16446(JiShuH
),所以是按照社保基数上限 16446(而不是用 20000) 去乘以社保的缴费比例计算实际缴纳的社保数额。
2. 员工工资数据文件说明
员工工资数据文件,即本实验中输入的数据文件。每位员工工资数据单独占一行,文件格式为 工号,税前工资
,举例如下:
3. 员工工资单数据文件说明
员工工资单数据文件,即本实验需要输出得到的数据文件。同样,输出的员工工资单数据文件中,每行格式为 工号,税前工资,社保金额,个税金额,税后工资
,举例如下:
需要特别注意的是:
-
上面只是示例输出(3 行数据),测试时候用的数据文件可能有更多行,输出的文件行数要与测试文件行数相同,但不需要保持相同的顺序。
-
程序的执行过程如下,配置文件
test.cfg
和输入的员工数据文件user.csv
需要自己创建并填入数据(可参考上述内容示例)。文件可以放在任何位置,只要参数中指定文件的路径就可以了,示例如下:
目标
完成任务需要达成的目标:
- 程序存放的位置
/home/shiyanlou/calculator.py
。 - 程序必须对文件是否存在,以及是否符合格式要求进行判断,如果有错误需要打印错误信息并退出。
- 程序返回的税后工资、个税及社保数字保留两位小数,如果是整数,仍然需要保存为
xxx.00
这种形式。
提示语
下述实现方案仅供参考,会涉及到先前实验中学习到的知识点,如果自己对程序有足够的理解也可以不按照下述提示编写
- 需要注意社保基数的处理,比如 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
参数也用这种方法获得。
- 首先使用
如果你阅读完提示之后,依旧没有思路。下面给出了一些示例代码。代码仅供参考,你可以按照自己的想法调整类以及类里面包含的函数。
# -*- coding: utf-8 -*-import sysimport csv # 用于写入 csv 文件# 处理命令行参数类class Args(object): def __init__(self): self.args = sys.argv[1:] """ 补充代码: 1. 补充参数读取函数,并返回相应的路径. 2. 当参数格式出错时,抛出异常. """# 配置文件类class Config(object): def __init__(self): self.config = self._read_config() # 配置文件读取内部函数 def _read_config(self): config = {} """ 补充代码: 1. 根据参数指定的配置文件路径,读取配置文件信息,并写入到 config 字典中. 2. 使用 strip() 和 split() 对读取到的配置文件去掉空格以及切分. 3. 当格式出错时,抛出异常. """# 用户数据类class UserData(object): def __init__(self): self.userdata = self._read_users_data() # 用户数据读取内部函数 def _read_users_data(self): userdata = [] """ 补充代码: 1. 根据参数指定的工资文件路径,读取员工 ID 和工资数据. 2. 可将员工工号和工资数据设置为元组,并存入 userdata 列表中. 3. 当格式出错时,抛出异常. """# 税后工资计算类class IncomeTaxCalculator(object): # 计算每位员工的税后工资函数 def calc_for_all_userdata(self): """ 补充代码: 1. 计算每位员工的税后工资(扣减个税和社保). 2. 注意社保基数的判断. 3. 将每位员工的税后工资按指定格式返回. """ # 输出 CSV 文件函数 def export(self, default='csv'): result = self.calc_for_all_userdata() with open("输出文件的路径,由输入参数获得") as f: writer = csv.writer(f) writer.writerows(result)# 执行if __name__ == '__main__': """ 按实际情况补充代码 """
通过代码:
1 #!/usr/bin/env python3 #这个不能漏,告诉用的是Python3 2 # _*_ coding: utf-8 _*_ #用的是utf-8编码 3 import sys #导入系统接口,调用命令行参数 4 import csv #导入纯文本文件模块 5 #这个类比较简单,就不加方法了 6 class Args: #定义一个类,从命令行获得路径的,类的()不是必须的,但必须首字母大写 7 def __init__(self): #定义初始化函数,self就是指向实例的,所以才有后面的 args.c 8 l = sys.argv[1:] #把命令行参数列表放到一个列表里面,从1开始是因为要除掉第一个./calculator.py 9 self.c = l[l.index('-c')+1] #获得-c的索引,+1就是路径,./calculator.py -c /home/shiyanlou/test.cfg -d /home/shiyanlou/user.csv -o /tmp/gongzi.csv 10 self.d = l[l.index('-d')+1]11 self.o = l[l.index('-o')+1]12 13 args = Args() #实例化一个类对象,才会自动调用__init__初始化函数,执行后面的内容,不实例化就不会执行 __init__,不获得数据14 args 在文件内相当于全局变量,任何地方都可以直接引用,此时用args代替self就可以调用类的属性, 15 class Config: #定义一个类,从配置路径里面获得配置文件 1617 def __init__(self): #定义初始化函数18 self.config = self._read_config() #因为 __init__ 用到了 _read_config ,所以后者也执行了一次19 20 def _read_config(self):21 d = { 's': 0} #s是比例22 with open(args.c) as f1: #只有 'r' 可以省略,其他都不行23 for line in f1.readlines(): #分行遍历读取f124 m = line.split('=') #以=分割成列表25 a, b = m[0].strip(), m[1].strip()#每一组第一个参数为key,第二个参数为value26 if a == 'JiShuL' or a == 'JiShuH':27 d[a] = float(b) #第一个和第二个放入字典28 else:29 d['s'] += float(b) #后面的依次放入字典,可以通过 d['s'] 获得比例,上下限也是30 return d #返回整个字典31 32 config = Config().config #config是字典,通过字典可以获取值,{JiShuL:2193.00,JiShuH:1646.00,‘s':0.165}33 #print(config1),怕弄混的话,可以后面加1做区分 34 35 def calc_for_all_userdata(salary):36 salary = int(salary)37 shebao = salary * config['s']38 if salary < config['JiShuL']:39 shebao = config['JiShuL'] * config['s']40 if salary > config['JiShuH']:41 shebao = config['JiShuH'] * config["s"]42 m = salary - shebao - 350043 if m <= 0:44 shui = 045 elif m <= 1500:46 shui = m * 0.0347 elif m <= 4500:48 shui = m * 0.1-10549 elif m <= 9000:50 shui = m * 0.2-55551 elif m <= 35000:52 shui = m * 0.25-100553 elif m <= 55000:54 shui = m * 0.3-275555 elif m <= 80000:56 shui = m * 0.35-550557 else:58 shui = m * 0.45-1350559 shuihou = salary - shebao - shui60 return [salary, format(shebao, '.2f'), format(shui, '.2f'), format(shuihou, '.2f')] #后面三位要求保留两位小数嘛,注意工资并不要求是float61 62 class Data: #定义工号类63 def __init__(self):64 with open(args.d) as f: #打开工号和工资csv文件,‘r’可省略65 l = list(csv.reader(f)) #reader是csv读取文件的方法,把文件内容读取出来成为列表66 self.value = l #实例的 value 值是列表 67 data = Data().value #这个列表赋值给 data 68 #print(data) #[[101,3500],[203,5000],[309,15000]]69 #现在我们已经可以获取全部数据了,再写一段把数据放到文件里的代码就完事儿了70 with open(args.o, 'w') as f: #打开args.o路径,没有则新增,w表示删除新增71 for a, b in data: #遍历data工号列表72 x = calc_for_all_userdata(b) #把工资代入,返回一个列表[3500,577.50,0.00,2922.50]73 x.insert(0, a) #在x列表索引为0的位置上插入工号a,也就是10174 csv.writer(f).writerow(x) #调用csv的写入方法,写入f,也就是输出的args.0里面,row表示一行