Argparse 教程¶
- 作者:
Tshepang Mbambo
本教程旨在温和地介绍 argparse
,这是 Python 标准库中推荐的命令行解析模块。
注意
标准库包括另外两个直接与命令行参数处理相关的库:较低级别的 optparse
模块(可能需要更多代码来为给定应用程序配置,但也允许应用程序请求 argparse
不支持的行为)和非常低级别的 getopt
(专门用作 C 程序员可用的 getopt()
函数系列的等效项)。虽然本指南中没有直接介绍这些模块,但 argparse
中的许多核心概念最初都源于 optparse
,因此本教程的某些方面也与 optparse
用户相关。
概念¶
让我们通过使用 ls 命令来展示我们将在本入门教程中探讨的功能类型
$ ls
cpython devguide prog.py pypy rm-unused-function.patch
$ ls pypy
ctypes_configure demo dotviewer include lib_pypy lib-python ...
$ ls -l
total 20
drwxr-xr-x 19 wena wena 4096 Feb 18 18:51 cpython
drwxr-xr-x 4 wena wena 4096 Feb 8 12:04 devguide
-rwxr-xr-x 1 wena wena 535 Feb 19 00:05 prog.py
drwxr-xr-x 14 wena wena 4096 Feb 7 00:59 pypy
-rw-r--r-- 1 wena wena 741 Feb 18 01:01 rm-unused-function.patch
$ ls --help
Usage: ls [OPTION]... [FILE]...
List information about the FILEs (the current directory by default).
Sort entries alphabetically if none of -cftuvSUX nor --sort is specified.
...
我们可以从这四个命令中学到几个概念
当不带任何选项运行时,ls 命令非常有用。它默认为显示当前目录的内容。
如果我们想要超出其默认提供的功能,我们会告诉它更多。在这种情况下,我们希望它显示一个不同的目录
pypy
。我们所做的是指定一个称为位置参数的内容。之所以这样命名,是因为程序应该仅根据它在命令行上的位置来知道如何处理该值。这个概念与像 cp 这样的命令更相关,其最基本用法是cp SRC DEST
。第一个位置是*你要复制什么,*第二个位置是*你要把它复制到哪里。*现在,假设我们想更改程序的行为。在我们的示例中,我们为每个文件显示更多信息,而不是只显示文件名。在这种情况下,
-l
被称为可选参数。这是帮助文本的片段。它非常有用,因为您可能会遇到一个以前从未用过的程序,并且只需阅读其帮助文本就可以弄清楚它是如何工作的。
基础知识¶
让我们从一个非常简单的示例开始,它(几乎)什么都不做
import argparse
parser = argparse.ArgumentParser()
parser.parse_args()
以下是运行代码的结果
$ python prog.py
$ python prog.py --help
usage: prog.py [-h]
options:
-h, --help show this help message and exit
$ python prog.py --verbose
usage: prog.py [-h]
prog.py: error: unrecognized arguments: --verbose
$ python prog.py foo
usage: prog.py [-h]
prog.py: error: unrecognized arguments: foo
这是正在发生的事情
在不带任何选项的情况下运行脚本会导致没有任何内容显示到 stdout。没什么用处。
第二个示例开始显示
argparse
模块的实用性。我们几乎没有做什么,但我们已经获得了一条不错的帮助消息。--help
选项(也可以缩写为-h
)是我们免费获得的唯一选项(即不需要指定它)。指定任何其他内容都会导致错误。但是即使那样,我们也会免费获得有用的用法消息。
介绍位置参数¶
一个例子
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("echo")
args = parser.parse_args()
print(args.echo)
并运行代码
$ python prog.py
usage: prog.py [-h] echo
prog.py: error: the following arguments are required: echo
$ python prog.py --help
usage: prog.py [-h] echo
positional arguments:
echo
options:
-h, --help show this help message and exit
$ python prog.py foo
foo
这是正在发生的事情
我们添加了
add_argument()
方法,我们用它来指定程序愿意接受哪些命令行选项。在这种情况下,我将其命名为echo
,以便与它的功能一致。现在调用我们的程序需要我们指定一个选项。
parse_args()
方法实际上从指定的选项返回一些数据,在本例中为echo
。该变量是
argparse
免费执行的某种形式的“魔法”(即,无需指定该值存储在哪个变量中)。您还会注意到它的名称与传递给该方法的字符串参数echo
匹配。
但是请注意,尽管帮助显示看起来不错,但它目前并没有那么有用。例如,我们看到我们得到了 echo
作为位置参数,但我们不知道它有什么作用,除了猜测或阅读源代码。因此,让我们使它更有用一些
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("echo", help="echo the string you use here")
args = parser.parse_args()
print(args.echo)
我们得到
$ python prog.py -h
usage: prog.py [-h] echo
positional arguments:
echo echo the string you use here
options:
-h, --help show this help message and exit
现在,如何做一些更有用的事情
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", help="display a square of a given number")
args = parser.parse_args()
print(args.square**2)
以下是运行代码的结果
$ python prog.py 4
Traceback (most recent call last):
File "prog.py", line 5, in <module>
print(args.square**2)
TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'int'
那进行得不太顺利。这是因为 argparse
将我们给它的选项视为字符串,除非我们另行告知。因此,让我们告诉 argparse
将该输入视为整数
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", help="display a square of a given number",
type=int)
args = parser.parse_args()
print(args.square**2)
以下是运行代码的结果
$ python prog.py 4
16
$ python prog.py four
usage: prog.py [-h] square
prog.py: error: argument square: invalid int value: 'four'
那进行得很好。现在,该程序甚至在继续操作之前,也会在遇到错误的非法输入时有帮助地退出。
介绍可选参数¶
到目前为止,我们一直在使用位置参数。让我们看一下如何添加可选参数
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--verbosity", help="increase output verbosity")
args = parser.parse_args()
if args.verbosity:
print("verbosity turned on")
以及输出
$ python prog.py --verbosity 1
verbosity turned on
$ python prog.py
$ python prog.py --help
usage: prog.py [-h] [--verbosity VERBOSITY]
options:
-h, --help show this help message and exit
--verbosity VERBOSITY
increase output verbosity
$ python prog.py --verbosity
usage: prog.py [-h] [--verbosity VERBOSITY]
prog.py: error: argument --verbosity: expected one argument
这是正在发生的事情
该程序的编写方式是,当指定
--verbosity
时显示内容,而不指定时则不显示任何内容。为了表明该选项实际上是可选的,在不使用该选项的情况下运行程序时不会出现错误。请注意,默认情况下,如果未使用可选参数,则为相关变量(在本例中为
args.verbosity
)赋予None
值,这就是它未能通过if
语句的真值测试的原因。帮助消息略有不同。
使用
--verbosity
选项时,还必须指定一些值,任何值。
上面的示例接受 --verbosity
的任意整数值,但是对于我们简单的程序,实际上只有两个值有用,True
或 False
。让我们相应地修改代码
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--verbose", help="increase output verbosity",
action="store_true")
args = parser.parse_args()
if args.verbose:
print("verbosity turned on")
以及输出
$ python prog.py --verbose
verbosity turned on
$ python prog.py --verbose 1
usage: prog.py [-h] [--verbose]
prog.py: error: unrecognized arguments: 1
$ python prog.py --help
usage: prog.py [-h] [--verbose]
options:
-h, --help show this help message and exit
--verbose increase output verbosity
这是正在发生的事情
该选项现在更像是一个标志,而不是需要值的标志。我们甚至更改了选项的名称以匹配该想法。请注意,我们现在指定了一个新的关键字
action
,并为其赋值"store_true"
。这意味着,如果指定了该选项,则将值True
赋值给args.verbose
。不指定它意味着False
。当您指定一个值时,它会抱怨,这符合标志的真实含义。
请注意不同的帮助文本。
短选项¶
如果您熟悉命令行用法,您会注意到我还没有涉及选项的短版本的主题。这很简单
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-v", "--verbose", help="increase output verbosity",
action="store_true")
args = parser.parse_args()
if args.verbose:
print("verbosity turned on")
这是结果
$ python prog.py -v
verbosity turned on
$ python prog.py --help
usage: prog.py [-h] [-v]
options:
-h, --help show this help message and exit
-v, --verbose increase output verbosity
请注意,新功能也反映在帮助文本中。
组合位置参数和可选参数¶
我们的程序不断增加复杂性
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", type=int,
help="display a square of a given number")
parser.add_argument("-v", "--verbose", action="store_true",
help="increase output verbosity")
args = parser.parse_args()
answer = args.square**2
if args.verbose:
print(f"the square of {args.square} equals {answer}")
else:
print(answer)
现在输出
$ python prog.py
usage: prog.py [-h] [-v] square
prog.py: error: the following arguments are required: square
$ python prog.py 4
16
$ python prog.py 4 --verbose
the square of 4 equals 16
$ python prog.py --verbose 4
the square of 4 equals 16
我们带回了一个位置参数,因此出现了抱怨。
请注意,顺序无关紧要。
我们如何让我们的程序恢复具有多个详细程度值的能力,并实际使用它们
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", type=int,
help="display a square of a given number")
parser.add_argument("-v", "--verbosity", type=int,
help="increase output verbosity")
args = parser.parse_args()
answer = args.square**2
if args.verbosity == 2:
print(f"the square of {args.square} equals {answer}")
elif args.verbosity == 1:
print(f"{args.square}^2 == {answer}")
else:
print(answer)
以及输出
$ python prog.py 4
16
$ python prog.py 4 -v
usage: prog.py [-h] [-v VERBOSITY] square
prog.py: error: argument -v/--verbosity: expected one argument
$ python prog.py 4 -v 1
4^2 == 16
$ python prog.py 4 -v 2
the square of 4 equals 16
$ python prog.py 4 -v 3
16
这些看起来都不错,除了最后一个,它暴露了我们程序中的一个错误。让我们通过限制 --verbosity
选项可以接受的值来修复它
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", type=int,
help="display a square of a given number")
parser.add_argument("-v", "--verbosity", type=int, choices=[0, 1, 2],
help="increase output verbosity")
args = parser.parse_args()
answer = args.square**2
if args.verbosity == 2:
print(f"the square of {args.square} equals {answer}")
elif args.verbosity == 1:
print(f"{args.square}^2 == {answer}")
else:
print(answer)
以及输出
$ python prog.py 4 -v 3
usage: prog.py [-h] [-v {0,1,2}] square
prog.py: error: argument -v/--verbosity: invalid choice: 3 (choose from 0, 1, 2)
$ python prog.py 4 -h
usage: prog.py [-h] [-v {0,1,2}] square
positional arguments:
square display a square of a given number
options:
-h, --help show this help message and exit
-v, --verbosity {0,1,2}
increase output verbosity
请注意,该更改也反映在错误消息和帮助字符串中。
现在,让我们使用一种不同的处理详细程度的方法,这很常见。它也与 CPython 可执行文件处理其自己的详细程度参数的方式相匹配(检查 python --help
的输出)
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", type=int,
help="display the square of a given number")
parser.add_argument("-v", "--verbosity", action="count",
help="increase output verbosity")
args = parser.parse_args()
answer = args.square**2
if args.verbosity == 2:
print(f"the square of {args.square} equals {answer}")
elif args.verbosity == 1:
print(f"{args.square}^2 == {answer}")
else:
print(answer)
我们引入了另一个操作“count”,以计算特定选项的出现次数。
$ python prog.py 4
16
$ python prog.py 4 -v
4^2 == 16
$ python prog.py 4 -vv
the square of 4 equals 16
$ python prog.py 4 --verbosity --verbosity
the square of 4 equals 16
$ python prog.py 4 -v 1
usage: prog.py [-h] [-v] square
prog.py: error: unrecognized arguments: 1
$ python prog.py 4 -h
usage: prog.py [-h] [-v] square
positional arguments:
square display a square of a given number
options:
-h, --help show this help message and exit
-v, --verbosity increase output verbosity
$ python prog.py 4 -vvv
16
是的,它现在更像是一个标志(类似于我们脚本的先前版本中的
action="store_true"
)。这应该可以解释抱怨。它的行为也类似于“store_true”操作。
现在这是对“count”操作提供的功能的演示。您可能以前见过这种用法。
如果你不指定
-v
标志,则该标志的值将被视为None
。正如预期的那样,指定标志的长格式,我们应该得到相同的输出。
遗憾的是,我们的帮助输出并没有很好地说明脚本新获得的功能,但这始终可以通过改进脚本的文档来解决(例如,通过
help
关键字参数)。最后一次输出暴露了我们程序中的一个错误。
让我们修复它
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", type=int,
help="display a square of a given number")
parser.add_argument("-v", "--verbosity", action="count",
help="increase output verbosity")
args = parser.parse_args()
answer = args.square**2
# bugfix: replace == with >=
if args.verbosity >= 2:
print(f"the square of {args.square} equals {answer}")
elif args.verbosity >= 1:
print(f"{args.square}^2 == {answer}")
else:
print(answer)
这就是它给出的结果
$ python prog.py 4 -vvv
the square of 4 equals 16
$ python prog.py 4 -vvvv
the square of 4 equals 16
$ python prog.py 4
Traceback (most recent call last):
File "prog.py", line 11, in <module>
if args.verbosity >= 2:
TypeError: '>=' not supported between instances of 'NoneType' and 'int'
第一次输出正常,并修复了我们之前的错误。也就是说,我们希望任何值 >= 2 时都尽可能详细。
第三次输出不太好。
让我们修复那个错误
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", type=int,
help="display a square of a given number")
parser.add_argument("-v", "--verbosity", action="count", default=0,
help="increase output verbosity")
args = parser.parse_args()
answer = args.square**2
if args.verbosity >= 2:
print(f"the square of {args.square} equals {answer}")
elif args.verbosity >= 1:
print(f"{args.square}^2 == {answer}")
else:
print(answer)
我们刚刚引入了另一个关键字,default
。我们将其设置为 0
,以便使其与其他 int 值可比较。请记住,默认情况下,如果未指定可选参数,则它将获得 None
值,并且该值无法与 int 值进行比较(因此会引发 TypeError
异常)。
并且
$ python prog.py 4
16
仅凭我们目前学到的知识,你就可以走得很远,而我们只是触及了皮毛。argparse
模块非常强大,在结束本教程之前,我们将对其进行更多探索。
更进一步¶
如果我们想扩展我们的小程序来执行其他幂运算,而不仅仅是平方,该怎么办
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("x", type=int, help="the base")
parser.add_argument("y", type=int, help="the exponent")
parser.add_argument("-v", "--verbosity", action="count", default=0)
args = parser.parse_args()
answer = args.x**args.y
if args.verbosity >= 2:
print(f"{args.x} to the power {args.y} equals {answer}")
elif args.verbosity >= 1:
print(f"{args.x}^{args.y} == {answer}")
else:
print(answer)
输出
$ python prog.py
usage: prog.py [-h] [-v] x y
prog.py: error: the following arguments are required: x, y
$ python prog.py -h
usage: prog.py [-h] [-v] x y
positional arguments:
x the base
y the exponent
options:
-h, --help show this help message and exit
-v, --verbosity
$ python prog.py 4 2 -v
4^2 == 16
请注意,到目前为止,我们一直在使用详细级别来更改显示的文本。以下示例改为使用详细级别来显示更多文本
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("x", type=int, help="the base")
parser.add_argument("y", type=int, help="the exponent")
parser.add_argument("-v", "--verbosity", action="count", default=0)
args = parser.parse_args()
answer = args.x**args.y
if args.verbosity >= 2:
print(f"Running '{__file__}'")
if args.verbosity >= 1:
print(f"{args.x}^{args.y} == ", end="")
print(answer)
输出
$ python prog.py 4 2
16
$ python prog.py 4 2 -v
4^2 == 16
$ python prog.py 4 2 -vv
Running 'prog.py'
4^2 == 16
指定模糊参数¶
当在决定参数是位置参数还是用于某个参数时存在歧义时,可以使用 --
来告诉 parse_args()
之后的所有内容都是位置参数
>>> parser = argparse.ArgumentParser(prog='PROG')
>>> parser.add_argument('-n', nargs='+')
>>> parser.add_argument('args', nargs='*')
>>> # ambiguous, so parse_args assumes it's an option
>>> parser.parse_args(['-f'])
usage: PROG [-h] [-n N [N ...]] [args ...]
PROG: error: unrecognized arguments: -f
>>> parser.parse_args(['--', '-f'])
Namespace(args=['-f'], n=None)
>>> # ambiguous, so the -n option greedily accepts arguments
>>> parser.parse_args(['-n', '1', '2', '3'])
Namespace(args=[], n=['1', '2', '3'])
>>> parser.parse_args(['-n', '1', '--', '2', '3'])
Namespace(args=['2', '3'], n=['1'])
冲突选项¶
到目前为止,我们一直在使用 argparse.ArgumentParser
实例的两种方法。让我们介绍第三种方法,add_mutually_exclusive_group()
。它允许我们指定彼此冲突的选项。我们还将更改程序的其余部分,以便新功能更有意义:我们将引入 --quiet
选项,它将与 --verbose
选项相反
import argparse
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument("-v", "--verbose", action="store_true")
group.add_argument("-q", "--quiet", action="store_true")
parser.add_argument("x", type=int, help="the base")
parser.add_argument("y", type=int, help="the exponent")
args = parser.parse_args()
answer = args.x**args.y
if args.quiet:
print(answer)
elif args.verbose:
print(f"{args.x} to the power {args.y} equals {answer}")
else:
print(f"{args.x}^{args.y} == {answer}")
我们的程序现在更简单了,并且为了演示的目的,我们失去了一些功能。无论如何,这是输出
$ python prog.py 4 2
4^2 == 16
$ python prog.py 4 2 -q
16
$ python prog.py 4 2 -v
4 to the power 2 equals 16
$ python prog.py 4 2 -vq
usage: prog.py [-h] [-v | -q] x y
prog.py: error: argument -q/--quiet: not allowed with argument -v/--verbose
$ python prog.py 4 2 -v --quiet
usage: prog.py [-h] [-v | -q] x y
prog.py: error: argument -q/--quiet: not allowed with argument -v/--verbose
这应该很容易理解。我添加了最后一个输出,以便你可以看到你获得的灵活性,即混合长格式选项和短格式选项。
在结束之前,你可能想告诉你的用户你的程序的主要目的,以防他们不知道
import argparse
parser = argparse.ArgumentParser(description="calculate X to the power of Y")
group = parser.add_mutually_exclusive_group()
group.add_argument("-v", "--verbose", action="store_true")
group.add_argument("-q", "--quiet", action="store_true")
parser.add_argument("x", type=int, help="the base")
parser.add_argument("y", type=int, help="the exponent")
args = parser.parse_args()
answer = args.x**args.y
if args.quiet:
print(answer)
elif args.verbose:
print(f"{args.x} to the power {args.y} equals {answer}")
else:
print(f"{args.x}^{args.y} == {answer}")
请注意用法文本中的细微差别。请注意 [-v | -q]
,它告诉我们可以使用 -v
或 -q
,但不能同时使用
$ python prog.py --help
usage: prog.py [-h] [-v | -q] x y
calculate X to the power of Y
positional arguments:
x the base
y the exponent
options:
-h, --help show this help message and exit
-v, --verbose
-q, --quiet
如何翻译 argparse 输出¶
argparse
模块的输出,例如其帮助文本和错误消息,都使用 gettext
模块进行翻译。这使应用程序可以轻松地本地化 argparse
生成的消息。另请参阅 国际化你的程序和模块。
例如,在这个 argparse
输出中
$ python prog.py --help
usage: prog.py [-h] [-v | -q] x y
calculate X to the power of Y
positional arguments:
x the base
y the exponent
options:
-h, --help show this help message and exit
-v, --verbose
-q, --quiet
字符串 usage:
、positional arguments:
、options:
和 show this help message and exit
都是可翻译的。
为了翻译这些字符串,必须首先将它们提取到 .po
文件中。例如,使用 Babel,运行此命令
$ pybabel extract -o messages.po /usr/lib/python3.12/argparse.py
此命令将从 argparse
模块中提取所有可翻译的字符串,并将它们输出到名为 messages.po
的文件中。此命令假设你的 Python 安装在 /usr/lib
中。
你可以使用此脚本找出系统上 argparse
模块的位置
import argparse
print(argparse.__file__)
自定义类型转换器¶
argparse
模块允许你为命令行参数指定自定义类型转换器。这允许你在用户输入存储到 argparse.Namespace
中之前修改用户输入。当你需要在程序中使用输入之前对其进行预处理时,这很有用。
使用自定义类型转换器时,你可以使用任何接受单个字符串参数(参数值)并返回转换值的可调用对象。但是,如果你需要处理更复杂的场景,你可以使用带有 action 参数的自定义操作类。
例如,假设你想处理带有不同前缀的参数并相应地处理它们
import argparse
parser = argparse.ArgumentParser(prefix_chars='-+')
parser.add_argument('-a', metavar='<value>', action='append',
type=lambda x: ('-', x))
parser.add_argument('+a', metavar='<value>', action='append',
type=lambda x: ('+', x))
args = parser.parse_args()
print(args)
输出
$ python prog.py -a value1 +a value2
Namespace(a=[('-', 'value1'), ('+', 'value2')])
在这个例子中,我们
使用
prefix_chars
参数创建了一个具有自定义前缀字符的解析器。定义了两个参数,
-a
和+a
,它们使用type
参数来创建自定义类型转换器,以将值存储在带有前缀的元组中。
如果没有自定义类型转换器,参数会将 -a
和 +a
视为同一参数,这是不希望的。通过使用自定义类型转换器,我们能够区分这两个参数。
结论¶
argparse
模块提供的功能远不止此处所示。它的文档非常详细和全面,并且充满了示例。在学习完本教程后,你应该能够轻松地理解它们,而不会感到不知所措。