在命令窗口运行 Python 程序时,有时需要传入一些参数,就用到了 argparse
模块,它有非常强的解析命令行参数的能力。本文对该包进行简要介绍:
sys.argv
;argparse
的基本用法;shell
脚本文件简化命令;configure.py
。sys.argv
是一个字符串列表,包含了在启动 Python 脚本时传递给解释器的命令行参数。第一个元素是脚本的名称(或路径),之后的元素是按顺序列出的命令行参数:
import sys
print(sys.argv)
print(f'Script name: {sys.argv[0]}') # 脚本的名称
print(f'Argument 1: {sys.argv[1]}') # 第一个命令行参数
print(f'Argument 2: {sys.argv[2]}') # 第二个命令行参数
在命令行中输入:
python .\argparse_demo.py arg1 arg2
输出:
['.\\argparse_demo.py', 'arg1', 'arg2']
Script name: .\argparse_demo.py
Argument 1: arg1
Argument 2: arg2
其实就是 ‘python’ 命令后的内容,按空格分割成了字符串列表。
argparse
基本用法先从一个简单的例子开始:
import argparse
parser = argparse.ArgumentParser(
prog='ProgramName',
description='What the program does',
epilog='Text at the bottom of help'
)
parser.add_argument('filename') # positional argument
parser.add_argument('-c', '--count') # option that takes avalue
parser.add_argument('-v', '--verbose', action='store_true') # on/off flag
args = parser.parse_args()
print(args)
命令行输入:python prog.py filename.txt -c 3 -v
,得到:
Namespace(filename='filename.txt', count='3', verbose=True)
命令行输入:python prog.py -h
,得到帮助信息:
usage: ProgramName [-h] [-c COUNT] [-v] filename # prog='ProgramName'
What the program does # description
positional arguments: # 位置参数
filename
optional arguments: # 可选参数
-h, --help show this help message and exit
-c COUNT, --count COUNT
-v, --verbose
Text at the bottom of help # epilog
首先要说明的是两类参数,positional arguments 和 optional arguments,这类似于函数签名中的位置参数和可选参数。此处:
- 不以
'-'
开头的普通变量是位置参数,其顺序由其添加顺序决定;- 以
'-'
或'--'
开头的是可选参数,变量名由--***
决定,如此例中的count
和verbose
,若没有--***
,则由-*
决定;后面将进行更详细的说明。
这段例子干了三件事:
parser = argparse.ArgumentParser(...)
;parser.add_argument(...)
添加了三个参数 filename
、count
、verbose
;args = parser.parse_args()
解析参数。现在要了解两个问题:
argparse.ArgumentParser
是什么?parser.add_argument
、args = parser.parse_args()
?argparse.ArgumentParser
是什么?打印解析器对象,print(parser)
输出的内容是:
ArgumentParser(
prog='ProgramName',
usage=None,
description='What the program does',
formatter_class=<class 'argparse.HelpFormatter'>,
conflict_handler='error',
add_help=True
)
可以看到这个对象现有六个参数,一眼就能看懂的有两个:
prog='ProgramName'
是程序脚本文件名,点到源码处,发现是这样的:# default setting for prog
if prog is None:
prog = _os.path.basename(_sys.argv[0])
程序名是可以设置的,不填的话,就默认为 _os.path.basename(_sys.argv[0])
[上面 sys.argv
有用]!但我们填了 prog='ProgramName'
。
description
字符串给出了程序的简要描述。然后可以从 python prog.py -h
的输出中,得知:
usage
:自定义用法说明。如果不指定,则会自动生成一个用法说明,如usage: ProgramName [-h] [-c COUNT] [-v] filename
虽然实际上这么输入命令是不可行的。看看我们自己设置 usage 会发生什么:
parser = argparse.ArgumentParser(usage='hahaha!!!')
再输出 help 信息,得到的 usage 为:
usage: hahaha!!!
add_help
:当 add_help=True
时(默认),可选 on/off 参数 -h, --help
会被添加到解析器中。可以通过在命令行中使用 -h
或 --help
来触发该可选参数,并显示帮助文档。这就是为什么会多出一个可选参数 -h, --help
来!如果改为 False
:parser = argparse.ArgumentParser(add_help=False)
再命令行输入 python prog.py --help
,则会报错,说明没有参数 --help
。
-h, --help
比较特别,给出了它,就会输出程序的帮助信息,包括:usage、description、positional arguments 和 optional arguments,而且不会执行脚本程序内容。
剩下的两个要求助 GPT:
formatter_class
:指定帮助文档输出的格式。常用的取值有 argparse.RawTextHelpFormatter
(保留原始文本格式)和 argparse.ArgumentDefaultsHelpFormatter
(包含参数的默认值)等。【主要是 description 和 epilog 换行规则吧!】conflict_handler
用于指定在出现冲突的情况下如何处理。具体来说,它用于解决当多个可选参数具有相同的参数名字符串时产生的冲突。接受以下值:ArgumentConflictError
异常;conflict_handler
参数的示例:import argparse
parser = argparse.ArgumentParser(conflict_handler='resolve')
parser.add_argument('-a', '--option', help='Option A')
parser.add_argument('-b', '--option', help='Option B') # -a-b 具有相同的可选参数字符串
args = parser.parse_args()
-a, --option
以及 -b, --option
都具有相同的可选参数字符串,即存在冲突。由于设置了 conflict_handler='resolve'
,后面出现的参数会覆盖先前出现的参数。因此,如果在命令行中提供了 -a 1 -b 2
,则 args.option
的值将是 '2'
。
小结:以上就是对 argparse.ArgumentParser
对象参数的解释,没有特别有用的地方。
parser.add_argument
?def add_argument(self,
*name_or_flags: str,
action: str | Type[Action] = ...,
nargs: int | str | _SUPPRESS_T | None = None,
const: Any = ...,
default: Any = ...,
type: (str) -> _T | FileType = ...,
choices: Iterable[_T] | None = ...,
required: bool = ...,
help: str | None = ...,
metavar: str | tuple[str, ...] | None = ...,
dest: str | None = ...,
version: str = ...,
**kwargs: Any
) -> Action
名称 | 描述 | 值 |
---|---|---|
action | 指明应当如何处理一个参数 | |
nargs | 参数可被使用的次数 |
|
const | 存储一个常量值 | |
default | 当未提供某个参数时要使用的默认值 | 默认为 |
type | 自动将参数转换为给定的类型 | |
choices | 将值限制为指定的可可选参数集合 | |
required | 指明某个参数是必需的还是可选的 |
|
help | 某个参数的帮助消息 | |
metavar | 要在帮助中显示的参数替代显示名称 | |
dest | 指定要在结果命名空间中使用的属性名称 |
函数 parser.add_argument(...)
向解析器中添加命令行参数,并通过上表中的设置告诉解析器如何解析该参数,如 type=int
告诉解析器应当把该参数解析为 int
整数。下面逐一解释这些设置:
*name_or_flags
设置参数名。根据 Python 的语法,这个 *name_or_flags
带 *
,是可变位置参数,可接收多个实参,也意味着可以给添加的参数取多个名字?来试试:
parser.add_argument('filename', 'name')
命令行 python prog.py file.txt
,报错:
ValueError: invalid option string 'filename': must start with a character '-'
无效的 option,即,多于一个的普通参数名,会被当作可选参数,且提示你应以 '-'
开头。
是的,上面的可选参数 '-c', '--count'
和 '-v', '--verbose'
,可以有多个名,可以有不止两个哦:
parser.add_argument('-c', '--count', '--cc', '---ddd')
可正常运行,且这四个名称都可用,不过最终的变量名由第一个横线数大于等于 2 的字符串决定,比如此处:args.count
。
action
& const
& default
指明应当如何处理一个可选参数,注意是可选参数,因为位置参数的灵活性很低,仅仅是简单地将出现在命令行中的非 -
字符串按顺序依次赋给位置参数,而可选参数要列出参数名,把要赋予的值跟在其后。
默认是 action='store'
,将跟在参数名后的字符串赋予该参数,如:
python prog.py filename.txt -c 3
将 3 赋给了 args.count
。这是最常见的,实际上函数的这三个参数搭配使用相当灵活。下面以 -c, --count
介绍常见的用法:
action='store'
时。将跟在 -c
后的 3 赋给 args.count
;如果可选参数 -c, --count
未列出到命令行,则会将 default=***
[默认default=None] 的值赋给 args.count
;parser.add_argument('-c', '--count', type=int, default=5)
则:
python prog.py filename.txt # args.count=5
action='store_const'
时。-c
后不必跟随任何值,跟了也会被赋给位置参数。args.count=const
if 命令行中出现 -c
else args.count=default
;【const=***
必须设置;此时不能再设置 type
,因为 const 已经确定了】action='store_true'
时。args.verbose=True
if 命令行中出现 -v
else args.verbose=False
;【等价于上面的 const=True, default=False
】简单来说,action='store_const'
时,const 和 default 二选一,action='store_true'
时,True 和 False 二选一。
append
系列很少用,区别在于 args.count
会被当作列表:python prog.py filename.txt -c 3 # args.count=[3]
python prog.py filename.txt -c 3 -c 2 # args.count=[3, 2]
append_const
就类比吧;count
就是计数:parser.add_argument('-c', '--count', action='count', default=5)
python prog.py file.txt # args.count=5
python prog.py file.txt -c -c -c # args.count=3
nargs
控制 -c
接收几个参数:
nargs=int>0
:接收 int
个参数parser.add_argument('-c', '--count', nargs=1)
python prog.py file.txt -c 1 # args.count=['1']
# 结果是 list
parser.add_argument('-c', '--count', nargs=3)
python prog.py file.txt -c 1 2 3 # args.count=['1', '2', '3']
nargs='?'
:接收至多一个参数parser.add_argument('-c', '--count', nargs='?', default=5)
python prog.py file.txt -c # args.count=None, default=5 不起作用
python prog.py file.txt -c 1 # args.count=['1']
nargs='+'
:至少一个;nargs='*'
:任意个。choices
固定参数可接受的值范围:
parser.add_argument('-c', '--count', type=int, choices=[1, 2, 3]) # 要和 type 一致
python prog.py file.txt -c 2 # args.count=2
python prog.py file.txt -c 4 # error
required
有点鸡肋,可选参数为什么还要搞出这么一个参数?大概是想实行 key=value
的参数供应形式,又不让 default
吧!
dest
& metavar
前面说参数名是 args.count
,那只是缺省这两个参数的情况。当:
parser.add_argument('-c', '--count', dest='true_name', metavar='help_name')
命令行输入 python prog.py file.txt --count 2
,得到【注意 true_name
】:
Namespace(filename='file.txt', true_name='2', verbose=False)
命令行输入 python prog.py -h
,得到【注意 help_name
】:
optional arguments:
-h, --help show this help message and exit
-c help_name, --count help_name
-v, --verbose
真正的参数名是由 dest=***
决定的,而 metavar
则是显示在帮助信息中。
函数 parser.add_argument(...)
的源码内容相当复杂,但我们知道 parser.parse_args()
返回了一个 Namespace
对象:
args = parser.parse_args() = Namespace(filename='file.txt', true_name='2', verbose=False)
包含了所有参数。然后还可以看到很多:
setattr(self, name, kwargs[name])
setattr(namespace, self.dest, not option_string.startswith('--no-'))
setattr(namespace, self.dest, values) # action='store'
setattr(namespace, self.dest, self.const) # action='store_const'
setattr(namespace, self.dest, items)
setattr(namespace, self.dest, items)
...
清一色的为 namespace
对象设置属性 self.dest
,就是刚才说的决定参数名的。上面各行应该就是对应不同的 action
。
以下代码是一个 Python 程序,它获取一个整数列表并计算总和或者最大值:
parser = argparse.ArgumentParser(description='Process some integers.')
parser.add_argument(
'integers',
metavar='N',
type=int,
nargs='+',
help='an integer for the accumulator'
)
parser.add_argument(
'--sum',
dest='accumulate',
action='store_const',
const=sum,
default=max,
help='sum the integers (default: find the max)'
)
args = parser.parse_args()
print(args.accumulate(args.integers))
当使用适当的参数运行时,它会输出命令行传入整数的总和或者最大值:
python prog.py 1 2 3 4
4
python prog.py 1 2 3 4 --sum
10
函数 sum
和 max
也可作为参数的赋值。
这个例子中涉及 metavar
、nargs
、dest
、action
等内容。
allow_abbrev
在不引起混淆的情况下,命令行参数允许缩写。
假如:
parser.add_argument('-opt')
那么命令行中写:
# 需要是 `opt` 的前缀
python prog.py -o 4
python prog.py -op 4
python prog.py -opt 4
都是一样的,得到变量名都是 args.opt
。
假如:
parser.add_argument('--option')
那么命令行中写:
python prog.py --o 4
python prog.py --op 4
python prog.py --opt 4
python prog.py --option 4
都是一样的,得到变量名都是 args.option
。
同时提供了 '-o', '--option'
的话,则最终的变量名为 'args.option'
,即双横杠的为变量名其他用法都一样。
parser.add_argument('-opt', '--option')
命令行中则只需写其前缀 -o, -op, -opt, ...
,当然,-o
最简单。
【注】关于变量名,add_argument
中的参数 dest
才是最牛的,只要指定 dest='***'
,那最终的变量名就是 args.***
,不论 '-o', '--option'
。
有一个问题:每次运行程序都要向命令行输入这么一串命令吗?参数众多的时候怎么办?一次还好,多次怎么办?也许说命令行是可以上下键翻找历史的,可是新的窗口好像不行。对于做实验调参数,参数记录也不方便。
那么用 shell 脚本文件存储命令是一种选择:
# command.sh
python prog.py filename.txt -c 3 -v
多个脚本文件,就可以方便地调参了。
参数众多时,也比较麻烦,你要写很长很长的命令。此时,可以把命令写到文本文件里,然后,在创建解析器对象时:
parser = argparse.ArgumentParser(fromfile_prefix_chars='@')
把参数写到文件 args.txt
里:
filename.txt
-c 3
-v
然后执行命令:python prog.py @args.txt
,即可达到同样的效果。【注意,一行一个参数!对于接收多个值的位置参数,也要分多行!】
虽然可以用文件的形式简化命令行,但还有两个问题:
args.name
编程不方便,由于参数 name
并不是编写代码时编给 Namespace
的,而是通过 setattr(...)
函数赋予的,故当敲除 args.
的时候,再强的开发环境都不会弹出 name
。。。假设在 main.py
中设置这些参数,即 main.py
中有一个叫 args
的命名空间变量,那我现在需要在 data.py
中写一个读取文件的函数 def read_data(...)
,或者说,更具包装性的数据类 class Data(object)
,其中必然用到诸如 file_name, batch_size
之类的一些参数,如何传递呢?如果:
# data.py
from main import args # 引入 args
class Data(object):
def __init__(self):
with open(args.filename, 'r') as f:
file_content = f.read()
self.content = file_content
# main.py
import argparse
from data import Data # 这里引入 Data
parser = argparse.ArgumentParser()
parser.add_argument('filename') # positional argument
parser.add_argument('-c', '--count') # option that takes avalue
parser.add_argument('-v', '--verbose', action='store_true') # on/off flag
args = parser.parse_args()
print(args)
d = Data()
当输入:python main.py file.txt -c 2 -v
时:
ImportError: cannot import name 'Data' from partially initialized module 'data' (most likely due to a circular import)
由 circular import
引发的错误。
那只能按函数形参的形式传递参数了!事实上,大部分程序是这么做的!当参数比较多时,就会出现一种现象:参数在以函数形参的形式在模块间来回传递,不够简洁美观。
args.filename
的提示?class Configure(object):
def __init__(self, config_path):
with open(config_path, 'r') as fjson:
json_config = json.load(fjson)
self.data_path = json_config['data_path']
self.model = json_config['model']
self.num_epochs = json_config['num_epochs']
self.batch_size = json_config['batch_size']
self.learning_rate = json_config['learning_rate']
self.device = torch.device(json_config['device'])
config = Configure('./configs/config.json')
再配上一个 json
文件:
{
"data_path": "...",
"model": "fcn",
"num_epochs": 50,
"batch_size": 2048,
"learning_rate": 0.001,
"device": "cuda:0",
}
就可以在全局使用 config.***
来访问配置参数了。当然这很低级!不像 argparse
那样有众多功能。