建立在多進程的基礎之上,使用模塊進行優化
介紹
優化上一個挑戰中完成的計算器,完善下述需求:
- 使用 getopt 模塊處理命令行參數
- 使用 Python3 中的 configparser 模塊讀取配置文件
- 使用 datetime模塊寫入工資單生成時間
計算器執行中包含下面的參數:
- -h 或 --help,打印當前計算器的使用方法,內容為:
Usage
: calculator
.py
-C cityname
-c configfile
-d userdata
-o resultdata
-
-C 城市名稱 指定使用某個城市的社保配置信息,如果沒有使用該參數,則使用配置文件中 [DEFAULT] 欄目中的數據,城市名稱不區分大小寫,比如配置文件中寫的是 [CHENGDU],這里參數可以寫 -C Chengdu,仍然可以匹配
-
-c 配置文件 配置文件,由于各地的社保比例稍有不同,我們將多個城市的不同配置信息寫入一個配置文件
-
-d 員工工資數據文件 指定員工工資數據文件,文件中包含兩列內容,分別為員工工號和工資金額
-
-o 員工工資單數據文件 輸出內容,將員工繳納的社保、稅前、稅后工資等詳細信息輸出到文件中
配置文件格式如下,數字不一定非常準確,僅供參考:
[DEFAULT
]
JiShuL
= 2193.00
JiShuH
= 16446.00
YangLao
= 0.08
YiLiao
= 0.02
ShiYe
= 0.005
GongShang
= 0
ShengYu
= 0
GongJiJin
= 0.06[CHENGDU
]
JiShuL
= 2193.00
JiShuH
= 16446.00
YangLao
= 0.08
YiLiao
= 0.02
ShiYe
= 0.005
GongShang
= 0
ShengYu
= 0
GongJiJin
= 0.06[BEIJING
]
JiShuL
= 4251.00
JiShuH
= 21258.00
YangLao
= 0.08
YiLiao
= 0.02
ShiYe
= 0.002
GongShang
= 0
ShengYu
= 0
GongJiJin
= 0.12
員工工資數據文件格式每行為 工號,稅前工資,舉例如下:
101,5000
203,6500
309,15000
輸出的員工工資單數據文件每行格式為 工號,稅前工資,社保金額,個稅金額,稅后工資,計算時間如下:
101,5000,825.00,0.00,4175.00,2019-02-01 12:09:32
203,6500,1072.50,12.82,5414.68,2019-02-01 12:09:32
309,15000,2475.00,542.50,11982.50,2019-02-01 12:09:32
計算時間為上一挑戰中實現的多進程代碼中的進程2計算的時間,格式為 年-月-日 小時:分鐘:秒。
程序的執行過程如下,注意配置文件和輸入的員工數據文件需要你自己創建并填入數據,可以參考上述的內容示例:
$
./calculator
.py
-C Chengdu
-c test
.cfg
-d user
.csv
-o gongzi
.csv
執行成功不需要輸出信息到屏幕,執行失敗或有異常出現則將錯誤信息輸出到屏幕
系統檢測說明
后臺有多個腳本對程序文件的路徑及運行結果進行檢測,如果嚴格按照實驗樓樓賽的標準只給出是否準確的反饋則非常不利于新手排錯調試,這里將后臺使用的部分測試用例提供出來,大家可以在遇到錯誤的時候先自行進行排錯,提供的部分測試用例僅供參考,如果有任何疑問可以在 QQ 討論組里與同學和助教進行交流。
如果 /home/shiyanlou/calculator.py 已經完成,點擊 提交結果 后遇到報錯,那么測試用例的臨時文件都會被保留,可以進入到測試文件夾中進行排錯。
首先,測試腳本會將 /home/shiyanlou/calculator.py 拷貝到 /tmp/test.py,然后會下載以下測試需要的文件:
/tmp/c5.cfg 配置文件
/tmp/c5user.csv 員工工資數據文件
執行的測試命令:
$ python3
/tmp
/test
.py
-C chengdu
-c
/tmp
/c5
.cfg
-d
/tmp
/c5user
.csv
-o
/tmp
/c5gongzi
.csv
排錯的時候可以進入到 /tmp 目錄,先檢查下輸出的文件 /tmp/c5gongzi.csv(員工工資單數據文件)是否存在,如果存在,重點檢查下里面的工號為 207 的員工稅后工資是否為 14467.77,這個地方是最容易出錯的地方,通常都是由于社保基數計算的問題導致的,可以確認下。
另外一個容易出錯的地方就是 -C chengdu 這個參數,需要注意以下幾點:
chengdu 大小寫都應該支持
可以準確的找到并從配置文件中加載 [CHENGDU] 這一個 section 的配置信息
如果挑戰 PASS 了,那么 /tmp 目錄下的測試文件都會被全部刪除
import sys
import csv
import configparser
from getopt
import getopt
, GetoptError
from datetime
import datetime
from collections
import namedtuple
import queue
from multiprocessing
import Queue
, Process
IncomeTaxQuickLookupItem
= namedtuple
('IncomeTaxQuickLookupItem',['start_point', 'tax_rate', 'quick_subtractor']
)
INCOME_TAX_START_POINT
= 5000
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
.options
= self
._options
()def _options(self
):"""內部函數,用來解析命令行選項,返回保存了所有選項及其取值的字典"""try:opts
, _
= getopt
(sys
.argv
[1:], 'hC:c:d:o:', ['help'])except GetoptError
:print('Parameter Error')exit
()options
= dict(opts
)if len(options
) == 1 and ('-h' in options
or '--help' in options
):print('Usage: calculator.py -C cityname -c configfile -d userdata -o resultdata')exit
()return options
def _value_after_option(self
, option
):"""內部函數,用來獲取跟在選項后面的值"""value
= self
.options
.get
(option
)if value
is None and option
!= '-C':print('Parameter Error')exit
()return value@
propertydef city(self
):"""城市"""return self
._value_after_option
('-C')@
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
= configparser
.ConfigParser
()config
.read
(args
.config_path
)if args
.city
and args
.city
.upper
() in config
.sections
():return config
[args
.city
.upper
()]else:return config
['DEFAULT']def _get_config(self
, key
):"""內部函數,用來獲得配置項的值"""try:return float(self
.config
[key
])except (ValueError
, KeyError
):print('Parameter 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(Process
):"""用戶工資文件處理進程"""def __init__(self
, userdata_queue
):super().__init__
()self
.userdata_queue
= userdata_queue
def _read_users_data(self
):"""內部函數,用來讀取用戶工資文件"""userdata
= []with open(args
.userdata_path
) as f
:for line
in f
.readlines
():employee_id
, income_string
= line
.strip
().split
(',')try:income
= int(income_string
)except ValueError
:print('Parameter Error')exit
()userdata
.append
((employee_id
, income
))return userdata
def run(self
):"""進程入口方法"""for item
in self
._read_users_data
():self
.userdata_queue
.put
(item
)class IncomeTaxCalculator(Process
):"""稅后工資計算進程"""def __init__(self
, userdata_queue
, export_queue
):super().__init__
()self
.userdata_queue
= userdata_queueself
.export_queue
= export_queue@
staticmethoddef calc_social_insurance_money(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@
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_subtractor
return '{:.2f}'.format(tax
), '{:.2f}'.format(real_income
- tax
)return '0.00', '{:.2f}'.format(real_income
)def calculate(self
, employee_id
, income
):"""計算單個用戶的稅后工資"""social_insurance_money
= '{:.2f}'.format(self
.calc_social_insurance_money
(income
))tax
, remain
= self
.calc_income_tax_and_remain
(income
)return [employee_id
, income
, social_insurance_money
, tax
, remain
,datetime
.now
().strftime
('%Y-%m-%d %H:%M:%S')]def run(self
):"""進程入口方法"""while True:try:employee_id
, income
= self
.userdata_queue
.get
(timeout
=1)except queue
.Empty
:returnresult
= self
.calculate
(employee_id
, income
)self
.export_queue
.put
(result
)class IncomeTaxExporter(Process
):"""稅后工資導出進程"""def __init__(self
, export_queue
):super().__init__
()self
.export_queue
= export_queueself
.file = open(args
.export_path
, 'w', newline
='')self
.writer
= csv
.writer
(self
.file)def run(self
):"""進程入口方法"""while True:try:item
= self
.export_queue
.get
(timeout
=1)except queue
.Empty
:self
.file.close
()returnself
.writer
.writerow
(item
)if __name__
== '__main__':userdata_queue
= Queue
()export_queue
= Queue
()userdata
= UserData
(userdata_queue
)calculator
= IncomeTaxCalculator
(userdata_queue
, export_queue
)exporter
= IncomeTaxExporter
(export_queue
)userdata
.start
()calculator
.start
()exporter
.start
()userdata
.join
()calculator
.join
()exporter
.join
()
2.getopt介紹
getopt這個函數 就是用來抽取 sys.argv 獲得的用戶輸入來確定執行步驟。
getopt是個模塊,而這個模塊里面又有getopt 函數,所以getopt需要這樣這樣用。
getopt
.getopt
( [命令行參數列表
], "短選項", [長選項列表
] )
該函數返回兩個值. opts 和args
opts 是一個存有所有選項及其輸入值的元組.當輸入確定后,這個值不能被修改了.
args 是去除有用的輸入以后剩余的部分.
import getopt
,sys
shortargs
= 'f:t'
longargs
= ['directory-prefix=', 'format', '--f_long=']
opts
,args
= getopt
.getopt
( sys
.argv
[1:], shortargs
, longargs
)
print 'opts=',opts
print 'args=',args
getopt函數的格式是getopt.getopt ( [命令行參數列表], “短選項”, [長選項列表] )
短選項名后的冒號(:)表示該選項必須有附加的參數。
長選項名后的等號(=)表示該選項必須有附加的參數。
try: opts
, args
= getopt
.getopt
(sys
.argv
[1:], "ho:", ["help", "output="])
except getopt
.GetoptError
:
解釋如下:
處理所使用的函數叫getopt() ,因為是直接使用import 導入的getopt 模塊,所以要加上限定getopt 才可以。使用sys.argv[1:] 過濾掉第一個參數(它是執行腳本的名字,不應算作參數的一部分)。使用短格式分析串"ho:" 。當一個選項只是表示開關狀態時,即后面不帶附加參數時,在分析串中寫入選項字符。當選項后面是帶一個附加參數時,在分析串中寫入選項字符同時后面加一 個":" 號 。所以"ho:" 就表示"h" 是一個開關選項;“o:” 則表示后面應該帶一個參數。使用長格式分析串列表:[“help”, “output=”] 。長格式串也可以有開關狀態,即后面不跟"=" 號。如果跟一個等號則表示后面還應有一個參數 。這個長格式表示"help" 是一個開關選項;“output=” 則表示后面應該帶一個參數。調用getopt 函數。函數返回兩個列表:opts 和args 。opts 為分析出的格式信息。args 為不屬于格式信息的剩余的命令行參數。opts 是一個兩元組的列表。每個元素為:( 選項串, 附加參數) 。如果沒有附加參數則為空串’’ 。整個過程使用異常來包含,這樣當分析出錯時,就可以打印出使用信息來通知用戶如何使用這個程序。
如上面解釋的一個命令行例子為:
‘-h -o file --help --output=out file1 file2’
在分析完成后,opts 應該是:
[(’-h’, ‘’), (’-o’, ‘file’), (’–help’, ‘’), (’–output’, ‘out’)]
而args 則為:
[‘file1’, ‘file2’]
轉載:https://www.cnblogs.com/chushiyaoyue/p/5380022.html
3.Python3中的configparser模塊
https://blog.csdn.net/atlansi/article/details/83243478
configparser模塊簡介
該模塊適用于配置文件的格式與windows ini文件類似,可以包含一個或多個節(section),每個節可以有多個參數(鍵=值)與java原先的配置文件相同的格式
看一下configparser生成的配置文件的格式
[DEFAULT
]
ServerAliveInterval
= 45
Compression
= yes
CompressionLevel
= 9
ForwardX11
= yes
[bitbucket
.org
]
User
= Atlan
[topsecret
.server
.com
]
Port
= 50022
ForwardX11
= no
現在看一下類似上方的配置文件是如何生成的
import configparser config
= configparser
.ConfigParser
() config
["DEFAULT"] = {'ServerAliveInterval': '45','Compression': 'yes','CompressionLevel': '9','ForwardX11':'yes'} config
['bitbucket.org'] = {'User':'Atlan'} config
['topsecret.server.com'] = {'Host Port':'50022','ForwardX11':'no'}with open('example.ini', 'w') as configfile
:config
.write
(configfile
)
解釋一下,操作方式
config
["DEFAULT"] = {'ServerAliveInterval': '45','Compression': 'yes','CompressionLevel': '9','ForwardX11':'yes'}
config
['bitbucket.org'] = {'User':'Atlan'}
和字典的操作方式相比,configparser模塊的操作方式,無非是在實例化的對象后面,跟一個section,在緊跟著設置section的屬性(類似字典的形式)
讀文件內容
import configparserconfig
= configparser
.ConfigParser
()print(config
.sections
()) config
.read
('example.ini')print(config
.sections
()) print('bytebong.com' in config
)
print('bitbucket.org' in config
) print(config
['bitbucket.org']["user"]) print(config
['DEFAULT']['Compression']) print(config
['topsecret.server.com']['ForwardX11']) print(config
['bitbucket.org']) for key
in config
['bitbucket.org']: print(key
)print(config
.options
('bitbucket.org')) print(config
.items
('bitbucket.org')) print(config
.get
('bitbucket.org','compression'))
修改
import configparserconfig
= configparser
.ConfigParser
()config
.read
('example.ini') config
.add_section
('yuan') config
.remove_section
('bitbucket.org')
config
.remove_option
('topsecret.server.com',"forwardx11") config
.set('topsecret.server.com','k1','11111')
config
.set('yuan','k2','22222')
with open('new2.ini','w') as f
:config
.write
(f
)
總結
以上是生活随笔為你收集整理的使用模块优化工资计算器的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。