0. 简介
在编程中,我们往往会遇到需要通过外部参数来控制脚本运行模式的情况,在通用的框架类代码中,这种情况尤为明显,因此,这里,我们来考察一下如何将参数传入到脚本文件中,而不是作为固定参数写死在脚本当中。
1. 使用sys库的暴力处理方法
使用sys库中的argv方法事实上可以可以暴力地读取出所有的python运行参数,给出代码范例如下:
import sys
if __name__ == "__main__":
for i, args in enumerate(sys.argv):
print("args {} -> {}\ttype::{}".format(i, args, type(args)))
当执行命令为:
python test.py 0 haha
脚本会打印出如下结果:
args 0 -> test.py type::<class 'str'>
args 1 -> 0 type::<class 'str'>
args 2 -> haha type::<class 'str'>
可以看到:
- 通过
sys.argv
,我们可以暴力地读取脚本的传入参数,但是其顺序必须实现确定,而且传入的参数均为str类型,且无法设置默认值。
因此,使用sys.argv
的方式进行参数传递是一种可行的方式,但是绝不是一种值得推荐的方法,更多的情况下,我们会使用argparse库来进行参数的传递。
2. argparse库的一般使用方法
argparse库是python自带的一种用于实现脚本的参数传递的函数库。
较之前述的sys.argv
暴力传参方法,argparse库具有以下优点:
- 接口上更为友好,可以通过key-value形式进行参数传递,用户更容易明白他传递的参数的具体含义;
- 参数传递包括但不限定于string类型,同样可以直接传递int或者float等数据类型;
- 可以给参数传递默认值,也可以设置参数是否为必须,防止因为漏传参数导致的脚本运行错误。
下面,我们给出argparse库的一般使用方式如下:
import argparse
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--input", type=str, required=True)
args = parser.parse_args()
print(args.input)
仿照上述方式,我们即可是实现9成以上的参数传递需求。
3. argparse参数
现在,我们来考察一下argparse更为细节的一些用法。
1. help内容
在使用argparse库进行参数传递时,我们可以通过help
来查询脚本的参数定义。
其调用方式如下:
python test_argparse.py -h
或者
python test_argparse.py --help
这两种方式都可以打印出argparse中的参数信息。
下面,我们考察如何写入信息。
其信息包括两部分:
- 整体的参数说明,这部分内容在创建parser时传入;
- 局部每一个参数的说明,这部分内容在添加每一个参数时通过help参数进行传入。
给出一个例子如下:
import argparse
if __name__ == "__main__":
parser = argparse.ArgumentParser("this is a test for help")
parser.add_argument("--input", help="input file path")
parser.add_argument("--mode", help="operation mode")
args = parser.parse_args()
打印出其信息得到:
usage: this is a test for help [-h] [--input INPUT] [--mode MODE]
optional arguments:
-h, --help show this help message and exit
--input INPUT input file path
--mode MODE operation mode
2. 参数种类与传入方式
下面,我们来看argparse所支持的参数种类与传入方式。
argparse的参数种类分为position arguments
与optional arguments
两类。两者类似于func(*args, **kwargs)
,前者只需要传入参数,后者以key-value
形式进行定义。
下面,给出其代码范例如下:
import argparse
if __name__ == "__main__":
parser = argparse.ArgumentParser("test argparse module")
parser.add_argument("mode", type=str, choices=["r", "w", "a"], help="running type")
parser.add_argument("file_type", type=str, choices=["txt", "csv", "log"], help="file type")
parser.add_argument("-i", "--input", type=str, help="input file")
args = parser.parse_args()
给出其脚本调用命令样例如下:
python test_argparse.py r txt -i=tmp.txt
其中,mode
与file_type
即为position arguments
,而input
则为optional arguments
。
可以看到,mode
和file_type
参数传递不需要给出参数名,但是必须要按照参数定义顺序进行参数传递,而input
不需要遵循参数定义顺序,但是必须要传入参数名。
此外,针对optional arguments
,参数名称可以设置简写,像上述例子中,即将input
的简称定义为了i
。
3. default参数与required参数
argparse库可以为参数设置是否必须以及默认值。
当一个参数被设置为必需时,如果为传入该参数则会发生报错。而通过设置默认值的方式,可以给参数设置默认值。
同样地我们给出代码样例如下:
import argparse
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("-a", type=int, required=True)
parser.add_argument("-b", type=int, required=False)
parser.add_argument("-c", type=int, required=True, default=3)
parser.add_argument("-d", type=int, required=False, default=4)
args = parser.parse_args()
print(args._get_kwargs())
测试发现:
- 如果不传入a和c参数,脚本会直接报错,说明
required
参数的默认值为True,且其判断优先级高于default
参数; - b与d参数可以不传入,不过不传入这两个参数,命名空间中依然会存在b与d,可以通过
args.b
与args.d
进行调用,但是在不传入参数的情况下,b的值为None,而d的值为给定的默认值4,某种意义上说,也可以认为default参数的默认参数为None。
4. 不同类型参数传入
现在,我们来考察不同类型的参数如何通过argparse库来进行传入。
1. 基本类型参数
argparse库的基本参数类型主要包含str
、int
以及float
三类,或者至少,我常用的包含上述三类。。。
下面,我们快速的给出上述三类的用法如下:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-a", type=str)
parser.add_argument("-b", type=int)
parser.add_argument("-c", type=float)
parser.add_argument("-d")
args = parser.parse_args()
print(args._get_kwargs())
另外需要注意的是,type的默认值为str。
2. 枚举类型参数
当我们希望我们的参数类型为枚举类型时,我们可以通过choices参数对可选参数进行限定。
给出代码样例如下:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-a", type=int, choices=[0,1,2])
args = parser.parse_args()
print(args._get_kwargs())
调用时,如果传入0、1、2之外的参数,则python会甩出报错如下:
usage: test_argparse.py [-h] [-a {0,1,2}]
test_argparse.py: error: argument -a: invalid choice: 4 (choose from 0, 1, 2)
3. bool类型参数
argparse库的基本参数类型中是不包括bool型的,因此,如果想要实现bool型的参数传递,我们往往需要一些小的trick。
一种暴力的做法就是通过上述枚举类型将输入参数限制在true与false当中,然后人为的进行字符串判断重新来完成bool类型的功能。
当然,上述说法我们可以通过下述的函数类型参数的方式来实现地更加优雅一些。
但即便如此,这终究是一个较为麻烦的方法,我们还是希望更为直接的可以直接定义bool型,甚至更进一步的,由于bool型本身就只有true和false两种状态,我们更希望可以做到:
- 当我们传入参数时,设置参数为true,否则则为false。
事实上,这个功能通过action
方法也是可以实现的。
我们给出代码样例如下:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--flag", action="store_true", default=False, required=False)
args = parser.parse_args()
print(args._get_kwargs())
在调用中,我们有如下结果:
$ python test_argparse.py
[('flag', False)]
$ python test_argparse.py --flag
[('flag', True)]
4. 数组类型参数
argparse库同样可以通过nargs
参数来实现数组类型的数据传递。
给出其代码样例如下:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-a", type=int, nargs="+")
args = parser.parse_args()
print(args._get_kwargs())
不过,需要注意的是,在进行参数传递时,我们不能够使用=
,否则会出现如下报错:
$ python test_argparse.py -a=1 2 3
usage: test_argparse.py [-h] [-a I [I ...]]
test_argparse.py: error: unrecognized arguments: 2 3 4
正确的参数传递方式应为如下形式:
$ python test_argparse.py -a 1 2 3
[('i', [1, 2, 3])]
5. 函数类型参数
更加一般性的,我们也可以令type参数等于一个方法,然后用其来替我们做一定的数据检查以及数据处理。
我们以上述的bool型为例,给出代码范例如下:
import argparse
def my_bool(arg : str):
if arg.lower() in ["true", "y"]:
return True
elif arg.lower() in ["false", "n"]:
return False
else:
raise argparse.ArgumentTypeError("wrong arg")
parser = argparse.ArgumentParser()
parser.add_argument("--flag", type=my_bool, required=False, default="true")
args = parser.parser_args()
print(args._get_kwargs())
通过这种方式,我们就可以将合法的string型输入转换为bool型输入参数,且同时完成对参数的数据检查,避免错误参数的传入。
4. 其他小tips
1. argparse参数的存储方式
argparse中的参数均是以key-value
形式存储于namespace中的,我们可以调用_get_kwargs()
方法进行查询。
另一方面,在参数添加时,如果同时存在简称-short_name
以及完整名称--full_name
时,namespace中的变量名会使用full_name
,反之,如果二者只存在其一时,则会使用该名称。
特别地,如果需要人为指定其在namespace中的名称,则我们可以通过参数dest
来进行人为指定。
给出代码样例如下:
-
只有简称
import argparse parser = argparse.ArgumentParser() parser.add_argument("-f", required=False) args = parser.parse_args() print(args._get_kwargs()) # ['f': None]
-
只有全称
import argparse parser = argparse.ArgumentParser() parser.add_argument("--flag", required=False) args = parser.parse_args() print(args._get_kwargs()) # ['flag': None]
-
同时有简称与全称
import argparse parser = argparse.ArgumentParser() parser.add_argument("-f", "--flag", required=False) args = parser.parse_args() print(args._get_kwargs()) # ['flag': None]
-
人为对参数命名进行指定
import argparse parser = argparse.ArgumentParser() parser.add_argument("-f", "--flag", dest="foo", required=False) args = parser.parse_args() print(args._get_kwargs()) # ['foo': None]
2. 参数默认值的设置方法
对于参数默认值的设定方法,除了在定义参数时使用default
参数进行传入,还可以在定义参数之后使用set_defaults
方法来对参数设置默认值。
给出代码样例如下:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-a", type=str)
parser.add_argument("-b", type=int)
parser.add_argument("-c", type=float)
parser.set_defaults(a="a", b=1, c=2.0)
args = parser.parse_args()
print(args._get_kwargs())
5. 后记
上述就是我对于使用argparse库进行参数传递的知识整理,算是结合了我的使用经验再补充上我能想到的一些可能会出现的问题进行的一个较为系统的知识整理。
其中肯定会有细节的知识点没有提到,不过一般来说应该是能cover住一般用户的绝大部分使用需求了。
如果有遗漏的话可以参考下述参考链接中的官方文档进行查询,也欢迎在留言区内进行补充。
当然,argparse
只是进行外部传参的其中一个常用库而已,事实上,使用tf.flags
也可以完成相同的功能,在机器学习领域,它或许会比argparse
更为常用,但这就是另外一个故事了,也许之后我会再写一个博客来介绍一下,但是这里就不再多做涉及了。