argparse

发布时间:2024年01月11日

0. Abstract

在命令窗口运行 Python 程序时,有时需要传入一些参数,就用到了 argparse 模块,它有非常强的解析命令行参数的能力。本文对该包进行简要介绍:

  • 原始的命令行参数获取方式 sys.argv
  • argparse 的基本用法;
  • 通过 shell 脚本文件简化命令;
  • 更好的方式 configure.py

1. sys.argv

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’ 命令后的内容,按空格分割成了字符串列表

2. 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,这类似于函数签名中的位置参数可选参数。此处:

  • 不以 '-' 开头的普通变量是位置参数,其顺序由其添加顺序决定;
  • '-''--' 开头的是可选参数,变量名由 --*** 决定,如此例中的 countverbose,若没有 --***,则由 -* 决定;

后面将进行更详细的说明。

这段例子干了三件事:

  • 创建参数解析器 parser = argparse.ArgumentParser(...)
  • 用函数 parser.add_argument(...) 添加了三个参数 filenamecountverbose
  • args = parser.parse_args() 解析参数。

现在要了解两个问题

  • argparse.ArgumentParser 是什么?
  • 如何添加和解析参数:parser.add_argumentargs = parser.parse_args()

1.1 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 用于指定在出现冲突的情况下如何处理。具体来说,它用于解决当多个可选参数具有相同的参数名字符串时产生的冲突。接受以下值:
    ???? ~~~~ ????‘error’:默认值。当出现冲突时,抛出一个 ArgumentConflictError 异常;
    ???? ~~~~ ????‘resolve’:自动解决冲突,默认选择后面的参数。这意味着后面出现的参数将覆盖先前出现的参数;
    ???? ~~~~ ????‘ignore’:忽略冲突,保留先前出现的参数,忽略后面出现的参数。
    下面是使用 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 对象参数的解释,没有特别有用的地方。

1.2 如何添加参数: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

指明应当如何处理一个参数

'store', 'store_const', 'store_true', 'append', 'append_const', 'count', 'help', 'version'

nargs

参数可被使用的次数

int, '?', '*''+'

const

存储一个常量值

default

当未提供某个参数时要使用的默认值

默认为 None

type

自动将参数转换为给定的类型

int, float, argparse.FileType('w') 或可调用函数

choices

将值限制为指定的可可选参数集合

['foo', 'bar'], range(1, 10)Container 实例

required

指明某个参数是必需的还是可选的

TrueFalse

help

某个参数的帮助消息

metavar

要在帮助中显示的参数替代显示名称

dest

指定要在结果命名空间中使用的属性名称

函数 parser.add_argument(...) 向解析器中添加命令行参数,并通过上表中的设置告诉解析器如何解析该参数,如 type=int 告诉解析器应当把该参数解析为 int 整数。下面逐一解释这些设置:

1.2.1 *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

1.2.2 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
1.2.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='*':任意个。
1.2.4 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
1.2.5 required

有点鸡肋,可选参数为什么还要搞出这么一个参数?大概是想实行 key=value 的参数供应形式,又不让 default 吧!

1.2.6 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 则是显示在帮助信息中。

1.3 如何解析参数?

函数 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

2. 其他

2.1 一个官方示例:可以给参数赋值任何对象

以下代码是一个 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

函数 summax 也可作为参数的赋值。

这个例子中涉及 metavarnargsdestaction 等内容。

2.2 ArgumentParser 中的参数 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'

2.3 命令脚本文件

有一个问题:每次运行程序都要向命令行输入这么一串命令吗?参数众多的时候怎么办?一次还好,多次怎么办?也许说命令行是可以上下键翻找历史的,可是新的窗口好像不行。对于做实验调参数,参数记录也不方便。

那么用 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,即可达到同样的效果。【注意,一行一个参数!对于接收多个值的位置参数,也要分多行!】

2.4 创建配置文件对象

虽然可以用文件的形式简化命令行,但还有两个问题:

  • 这些参数如何传给其他模块?
  • 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 引发的错误。

那只能按函数形参的形式传递参数了!事实上,大部分程序是这么做的!当参数比较多时,就会出现一种现象:参数在以函数形参的形式在模块间来回传递,不够简洁美观。

2.5 如何弹出 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 那样有众多功能。

文章来源:https://blog.csdn.net/qq_32527569/article/details/135440060
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。