-- TOC --
用Python编写一些命令行小工具,配合标准库中的argparse模块,可以实现非常漂亮和专业的命令行参数的实现。
一些简单的命令行,直接读取sys.argv也可以了。复杂一些的,才需要使用argparse模块。
C语言一般使用getopt接口来实现命令参数。
添加的不带-
或--
的参数,就是固定参数。
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('echo')
args = parser.parse_args()
print(args.echo)
以上测试代码定义了一个固定位置参数echo,由于是必须出现的参数,程序后面在打印echo参数的值的时候,不需要判断是否配置了这个参数。因为如果没有配置,parser.parse_args()这个函数就会报错。
$ python3 arg.py
usage: arg.py [-h] echo
arg.py: error: the following arguments are required: echo
$ python3 arg.py abcde
abcde
$ python3 arg.py 'abcde 12345'
abcde 12345
固定位置参数在命令行中的出现和配置顺序,与代码中参数的定义顺序是对应的。如果定义了多个位置参数,命令行输入参数的时候,顺序要与多个参数的定位顺序保持一致!
添加的带-
或--
的参数,属于可选参数。
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-s','--sn')
args = parser.parse_args()
if args.sn: print(args.sn)
else: print('no argument')
这里有一个约定俗成的惯例:单个字母只是用一个-
,多个字母使用--
。python也支持一个-
后面跟多个字母,不过看起来有关怪异。上面的定义 -s 显然是 --sn 的简写。
我们也可以只定义 -s 简写,或者之定义 --sn 这种,代码直接去掉一个参数即可。在两者都有的情况下,后面的代码引用参数的值,需要使用--
后面的那个词。
$ python3 arg.py
no argument
$ python3 arg.py -s 123
123
$ python3 arg.py --sn abc
abc
其实,可选参数也可以变成必选参数!必选参数跟位置参数还不一样,必选参数的位置可以比较灵活。
固定参数,可选参数,必选参数,都是我自己取的名字,还有下面的多值参数。:)
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-s', required=True)
parser.add_argument('-e','--echo')
args = parser.parse_args()
if args.s: print(args.s)
if args.echo: print(args.echo)
这段代码 -s 参数是必须参数,但是它出现的位置是灵活的,因为使用了required=True
。
$ python3 arg.py -h
usage: arg.py [-h] -s S [-e ECHO]
optional arguments:
-h, --help show this help message and exit
-s S
-e ECHO, --echo ECHO
$ python3 arg.py -s 123
123
$ python3 arg.py -s 123 -e abc
123
abc
$ python3 arg.py -e abc
usage: arg.py [-h] -s S [-e ECHO]
arg.py: error: the following arguments are required: -s
$ python3 arg.py -e abc -s 123
123
abc
-e 和 -s 出现的位置可以调换,而且 -s 必须要有。
命令行参数的值,可以定义类型,可以是任意类型。
type=str
,这个是默认值,前面的测试代码已经有示例。
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('echo', type=int)
args = parser.parse_args()
print(args.echo)
print(type(args.echo))
修改为type=int
后的测试:
E:\py>python arg.py 123
123
<class 'int'>
E:\py>python arg.py "123 456"
usage: arg.py [-h] echo
arg.py: error: argument echo: invalid int value: '123 456'
实际上就是将命令行参数值的字符串,尝试转换成int,如果不成功,就抛出异常。
这里可以通过type,实现任何类型。因为type后面接的,其实是一个函数接口,将用户输入的字符串进行转换的函数接口。
下面的示例,在命令行输入一个0x开始的十六进制字节数,转换函数实现0x开始的十六进制字节到int的转换:
import argparse
def take1byte(string):
"""change one-hex-byte to a real byte number"""
check_list = ['0','1','2','3','4','5','6','7','8','9',
'A','B','C','D','E','F']
if (len(string) != 4 or
string[0:2] != '0x' or
string[2] not in check_list or
string [3] not in check_list):
msg = 'Not a valid one-hex-byte with 0x prefix, in upper case.'
raise argparse.ArgumentTypeError(msg)
return int(string[2:],16)
parser = argparse.ArgumentParser()
parser.add_argument('--hex', required=True, type=take1byte)
args = parser.parse_args()
print('your input number is :', str(args.hex))
下面是这段测试代码的运行效果:
E:\py>python arg.py --hex=0x10
your input number is : 16
E:\py>python arg.py --hex=0xAA
your input number is : 170
E:\py>python arg.py --hex=0xFF
your input number is : 255
E:\py>python arg.py --hex=FF
usage: arg.py [-h] --hex HEX
arg.py: error: argument --hex: Not a valid one-hex-byte with 0x prefix, in upper
case.
type=take1byte
,转换函数take1byte函数的入参是一个字符串,如果在解析参数的时候出现错误,通过抛出argparse.ArgumentTypeError异常来实现命令行界面的提示信息,最后return返回真正的参数值。
再来一个示例,这个参数通过1+2+3...这样的方式获取参数,数字范围从1到12,可以重复+某个数字,转换函数将其转换成一个集合set类型:
import argparse
def takeslotset(string):
"""get slot set"""
slot_set_str = set(string.split('+'))
slot_set = set()
for item in slot_set_str:
if item not in ['1','2','3','4','5','6','7','8','9','10','11','12']:
raise argparse.ArgumentTypeError(f'slot number error:{item}')
slot_set.add(int(item))
return slot_set
parser = argparse.ArgumentParser()
parser.add_argument('--slot', required=True, type=takeslotset)
args = parser.parse_args()
print('slot set:', args.slot)
运行效果如下:
E:\py>python arg.py --slot=1+2+3
slot set: {1, 2, 3}
E:\py>python arg.py --slot=1+2+3+12
slot set: {1, 2, 3, 12}
E:\py>python arg.py --slot=1+2+3+12+24
usage: arg.py [-h] --slot SLOT
arg.py: error: argument --slot: slot number error:24
E:\py>python arg.py --slot=1+2+3+3+3+3
slot set: {1, 2, 3}
--slot=1+2+3,与--slot 1+2+3等价。命令行参数值可以用=号指定(无空格),也可以用一个空格分割。
argparse.FileType
是一个特殊的type类型,它可以用来打开一个stream,作为命令行参数的值。
import sys
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('infile', nargs='?',
type=argparse.FileType(), # default is 'r'
help='give me a file here, i open it and reture to you',
default=sys.stdin)
parser.add_argument('outfile', nargs='?',
type=argparse.FileType('w'),
help='give me a file here, i write it for you with in file',
default=sys.stdout)
args = parser.parse_args()
with args.infile as f, args.outfile as g:
g.write(f.read())
infile和outfile都是固定位置的可选参数,用nargs='?'来实现。
当这两个参数都不指定的时候,他们默认分别指向stdin和stdout。
运行效果如下:
$ echo 'abcde12345' > t1.txt
$ python3 arg.py t1.txt t2.txt
$ cat t2.txt
abcde12345
$ echo -e 'abcde\n12345' | python3 arg.py
abcde
12345
$ python3 arg.py < t1.txt
abcde12345
FileType objects understand the
pseudo-argument -
and automatically convert this into sys.stdin for readable FileType objects and sys.stdout for writable FileType objects. 原来-
是一个命令行伪参数,它代表stdin或stdout。
上面的测试代码,使用伪参数的效果:
$ python3 arg.py - -
1234567890 # input by hand
1234567890 # output to stdout
所谓多值参数,即某个命令行参数的值,可以有多个,每个值通过空格分开。这需要使用nargs
参数。
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--one', nargs=1, required=True)
args = parser.parse_args()
print(args.one)
nargs=1,表示one参数只能而且必须有1个输入值。
E:\py>python arg.py --one 1
['1']
E:\py>python arg.py --one 1 2 3
usage: arg.py [-h] --one ONE
arg.py: error: unrecognized arguments: 2 3
args.one
是一个list,只有一个输入值时,也是list。
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--three', nargs=3, required=True)
args = parser.parse_args()
print(args.three)
以上nargs=3,表示必须有3个输入值,不等于3个输入值,都是错误:
E:\py>python arg.py --three 1 2 3
['1', '2', '3']
E:\py>python arg.py --three 1 2 3 4 5
usage: arg.py [-h] --three THREE THREE THREE
arg.py: error: unrecognized arguments: 4 5
nargs=?
,表示0个或1个输入值,当没有输入值时,代码对此命令行参数赋None;
nargs=*
,表示0个或多个输入值;
nargs=+
,表示1个或多个输入值;
nargs= argparse.REMAINDER
,表示后面所有的输入值,都属于这个命令行参数,比如当某个值带有-
或--
时,为了避免混淆,此时就要这样用nargs。
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--remainder', nargs=argparse.REMAINDER, required=True)
args = parser.parse_args()
print(args.remainder)
运行效果如下:
E:\py>python arg.py --remainder
[]
E:\py>python arg.py --remainder 1
['1']
E:\py>python arg.py --remainder 1 2 3 4 5
['1', '2', '3', '4', '5']
E:\py>python arg.py --remainder 1 2 3 4 5 -k 1 2 3 4 5
['1', '2', '3', '4', '5', '-k', '1', '2', '3', '4', '5']
E:\py>python arg.py --remainder ls -lh
['ls', '-lh']
action用来指定某个命令行参数的“动作”。
默认值为action='store'
,表示保存用户输入的值(转换使用type),如果是一个可选参数,用户没有输入,此时为None。
命令行参数后面,不一定需要有值,配与不配,就已经不同了。貌似有一个词来形容这个情况,叫做模式选项。
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-s', action='store_true')
parser.add_argument('-e', action='store_true')
args = parser.parse_args()
if args.s: print('have s')
if args.e: print('have e')
以上代码定义了两个模式选项,-s 和 -e,需要配合action='store_true'
这个参数,表示只要配置了,对应的值就是True,不配置就是False(None的真值判断是False)。如下是这段代码在命令行调用的情况:
$ python3 arg.py -s -e
have s
have e
$ python3 arg.py -e -s
have s
have e
$ python3 arg.py -es
have s
have e
$ python3 arg.py -se
have s
have e
-s 和 -e可以分开,也可以混在一起,打乱顺序也没关系。模式选项这种命令行参数风格,来自Unix,python的argparse模块支持得还不错。
与store_true反过来,如果配置了,值为False。
有add_argument接口的const参数提供命令行参数的值。
$ cat arg_action.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--const', action='store_const', const=123)
args = parser.parse_args()
print(args.const)
$ python3 arg_action.py --const
123
const也可以等于一个字符串,如果没有配置,args.const就是None。
将命令行参数值放入list对象中,并且,允许这个命令行参数多次出现(nargs参数实现一个命令行参数对应多个值,但是不允许同一个命令行参数多次出现)。
$ cat arg_action.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--tolist', action='append')
args = parser.parse_args()
print(args.tolist)
$ python3 arg_action.py --tolist 1 --tolist 2 --tolist 3
['1', '2', '3']
如果在配合上nargs='+'参数,效果如下:
$ cat arg_action.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--tolist', action='append', nargs='+')
args = parser.parse_args()
print(args.tolist)
$ python3 arg_action.py --tolist 1 2 --tolist 3 4 5 --tolist 6 7
[['1', '2'], ['3', '4', '5'], ['6', '7']]
出现了嵌套多个list的效果。
这个action用来统计命令行参数出现的次数,出现次数不同,含义也不一样:
$ cat arg_action.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-c', '--cc', action='count')
args = parser.parse_args()
print(args.cc)
xinlin@ubuntu:~/test$ python3 arg_action.py -c
1
xinlin@ubuntu:~/test$ python3 arg_action.py -ccccc
5
xinlin@ubuntu:~/test$ python3 arg_action.py --cc --cc --cc
3
提供软件的版本信息:
$ cat arg_action.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-c', '--cc', action='count')
parser.add_argument('-V', action='version', version='cs.pynote.net V1.2345')
args = parser.parse_args()
print(args.cc)
$ python3 arg_action.py -V
pynote.net V1.2345
前面介绍的action='append'时,多次出现的同一个命令行参数,参数值保存成了嵌套list的形式。action='extend'优化了此问题,让多个list以extend的方式合并成一个list。
$ cat arg_action.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--onelist', action='extend', nargs='+')
parser.add_argument('-V', action='version', version='pynote.net V1.2345')
args = parser.parse_args()
print(args.onelist)
$ python3 arg_action.py --onelist 1 2 3 --onelist 4 5 6
['1', '2', '3', '4', '5', '6']
...
用python编写一个命令行接口的程序,少不了 -h 时的丰富的命令行信息显示,这就需要 ArgumentParser 对象使用description和epilog参数。description参数显示在usage和命令行参数(positional或optional)之间,我喜欢用这个区域来写程序的usage example。epilog参数显示在最后,我喜欢用这个区域留下自己的github页面和博客链接。
import argparse
parser = argparse.ArgumentParser(
description = 'this is description',
epilog = 'cs.pynote.net')
parser.print_help()
运行效果如下:
E:\py>python cmd_info.py
usage: cmd_info.py [-h]
this is description
optional arguments:
-h, --help show this help message and exit
cs.pynote.net
description部分好好写的haul,一般要写不少内容,会有很多行,默认情况下,ArgumentParser对象会对多行内容进行line-wrap,就是把所有的换行和连续的空格都变成一个空格。同时,我们还希望能够完美保留代码中的格式。
import argparse
import textwrap
parser = argparse.ArgumentParser(
formatter_class = argparse.RawDescriptionHelpFormatter,
description = textwrap.dedent('''\
this is description
which have a few lines and many line
feed and space...this is description
which have a few lines and many line
feed and space...
'''),
epilog = 'cs.pynote.net')
parser.print_help()
textwrap也是标准库中的模块,dedent表示取消indent。
运行效果如下:
E:\py>python cmd_info.py
usage: cmd_info.py [-h]
this is description
which have a few lines and many line
feed and space...this is description
which have a few lines and many line
feed and space...
optional arguments:
-h, --help show this help message and exit
cs.pynote.net
给命令行参数,设置一个值的选择范围。
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--cc', choices=('1','2','3'))
args = parser.parse_args()
--cc这个命令行参数的取值范围是'1','2'和'3',没有别的。
D:\temp>python test.py --cc 2
D:\temp>python test.py --cc 4
usage: test.py [-h] [--cc {1,2,3}]
test.py: error: argument --cc: invalid choice: '4' (choose from '1', '2', '3')
choices后来跟一个iterable,这样就可以是一个字符串对象了:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--cc', choices='abc')
args = parser.parse_args()
运行效果如下:
D:\temp>python test.py --cc a
D:\temp>python test.py --cc d
usage: test.py [-h] [--cc {a,b,c}]
test.py: error: argument --cc: invalid choice: 'd' (choose from 'a', 'b', 'c')
default表示当某一个可选参数没有出现在命令行时(没有配置),此时,这个参数的默认值,default的默认值为None,即默认情况下,所有没有配置的参数,经过parse_args调用后,其值为None。
注意default与store_const的区别,后者表示如果配置了,附上const对应的值,如果没有配置,默认就是None。
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-d', default='abc')
args = parser.parse_args()
print(args.d)
运行效果:
D:\temp>python test.py -d 123
123
D:\temp>python test.py
abc
default值需要与type对应起来,也需要与nargs之的含义对应起来,否则代码会报错。
default有一个特殊的值,argparse.SUPPRESS
,它表示当这个命令行参数没有配置的时候,连None这个值都不要给了,直接让它不存在。
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--echo', default=argparse.SUPPRESS)
args = parser.parse_args()
try:
print(args.echo)
except Exception as e:
print(repr(e))
运行效果如下:
D:\py>python arg_default.py --echo 123
123
D:\py>python arg_default.py
AttributeError("'Namespace' object has no attribute 'echo'")
是否使用这个特殊的值,就看你的代码逻辑了,哪种方式让代码看起来更优雅...
所谓互斥参数,就是在某几个命令行参数中,用户只能选择其中一个输入。
import argparse
def main():
parser = argparse.ArgumentParser()
timeType = parser.add_mutually_exclusive_group(required=True)
timeType.add_argument('--day', type=int,
help='keep the last x days files')
timeType.add_argument('--month', type=int,
help='keep the last x months files')
timeType.add_argument('--year', type=int,
help='keep the last x years files')
args = parser.parse_args()
print(args.day)
print(args.month)
print(args.year)
if __name__ == '__main__':
main()
以上测试代码,实现了一组互斥参数,required=True
的功能,与前面的介绍相同。
运行效果如下:
D:\temp>python test.py -h
usage: test.py [-h] (--day DAY | --month MONTH | --year YEAR)
optional arguments:
-h, --help show this help message and exit
--day DAY keep the last x days files
--month MONTH keep the last x months files
--year YEAR keep the last x years files
D:\temp>python test.py --day 1
1
None
None
D:\temp>python test.py --month 2
None
2
None
D:\temp>python test.py --year 3
None
None
3
add_argument函数的metavar参数,用来控制命令行参数的显示,注意:它只是影响参数的显示信息,不影响代码内部获取命令行参数的对象名称。
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-d', metavar='DDD')
args = parser.parse_args()
print(args.d)
如果没有metavar='DDD'
,显示为-d D
,现在的显示如下:
D:\temp>python test.py -h
usage: test.py [-h] [-d DDD]
optional arguments:
-h, --help show this help message and exit
-d DDD
当与nargs配合的时候,metavar要写成一个list,或tuple。
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-d', nargs=2, metavar=('D1','D2'))
args = parser.parse_args()
print(args.d)
运行效果:
D:\temp>python test.py -h
usage: test.py [-h] [-d D1 D2]
optional arguments:
-h, --help show this help message and exit
-d D1 D2
用dest可以改变在代码中获取命令行参数值的对象名称。
前面的测试代码已经充分演示了,通过args的属性对象获取命令行参数的值,而这个属性对象的名称,默认为使用add_argument接口注册的名称,基本与命令行上显示的名称一直。如果绝对代码不够优雅,可以用dest来修改这个名称。
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-d', dest='pump')
args = parser.parse_args()
print(args.pump)
用了dest,代码就要使用args.pump这个含义更明确的属性来获取参数值,可以提到代码的可读性。
这是add_argument接口的一个参数,用来指定程序名称,默认的程序名称,就是这个python代码文件的名称,带.py后缀。
import argparse
parser = argparse.ArgumentParser(prog='abc')
parser.add_argument('-d', dest='pump')
args = parser.parse_args()
print(args.pump)
运行效果,注意看abc出现的位置:
D:\temp>python test.py -h
usage: abc [-h] [-d PUMP]
optional arguments:
-h, --help show this help message and exit
-d PUMP
貌似一般情况下,这个功能都用不上。
首先要搞清楚什么是子明玲玲sub-command?
其实,我们每天都在使用的git,就是典型的sub-command命令行。
比如:git add, git push, git rebase......这些命令行中的add,push,rebase都叫做sub-command!(他们可不是固定位置的参数哦,每一个sub-command都有一整套属于自己的其它可选或必选参数等)
python自带的argparse模块,也可以让我们实现sub-command的功能。
import sys
import argparse
parser = argparse.ArgumentParser()
# sub command definition
subparser = parser.add_subparsers(dest='subcmd',
title='sub-commands')
parser_inline = subparser.add_parser('inline')
parser_infile = subparser.add_parser('infile')
# sub command inline
parser_inline.add_argument('-a')
# sub command infile
parser_infile.add_argument('-b')
args = parser.parse_args()
以上测试代码,定义了两个sub-command,分别是inline和infile,然后再分别对inline和infile进行命令行参数的定义。
运行效果如下:
D:\temp>python test.py -h
usage: test.py [-h] {inline,infile} ...
optional arguments:
-h, --help show this help message and exit
sub-commands:
{inline,infile}
D:\temp>python test.py inline -h
usage: test.py inline [-h] [-a A]
optional arguments:
-h, --help show this help message and exit
-a A
D:\temp>python test.py infile -h
usage: test.py infile [-h] [-b B]
optional arguments:
-h, --help show this help message and exit
-b B
本文链接:https://cs.pynote.net/sf/python/202208222/
-- EOF --
-- MORE --