Argparse 教程

    Tshepang Lekhonkhobe

    这篇教程旨在作为 argparse 的入门介绍,此模块是 Python 标准库中推荐的命令行解析模块。

    注解

    还有另外两个模块可以完成同样的任务,称为 (对应于 C 语言中的 函数) 和被弃用的 optparse。还要注意 是基于 optparse 的,因此用法与其非常相似。

    让我们利用 ls 命令来展示我们将要在这篇入门教程中探索的功能:

    我们可以从这四个命令中学到几个概念:

    • ls 是一个即使在运行的时候没有提供任何选项,也非常有用的命令。在默认情况下他会输出当前文件夹包含的文件和文件夹。

    • 如果我们想要使用比它默认提供的更多功能,我们需要告诉该命令更多信息。在这个例子里,我们想要查看一个不同的目录,pypy。我们所做的是指定所谓的位置参数。之所以这样命名,是因为程序应该如何处理该参数值,完全取决于它在命令行出现的位置。更能体现这个概念的命令如 cp,它最基本的用法是 cp SRC DEST。第一个位置参数指的是你想要复制的,第二个位置参数指的是你想要复制到的位置

    • 现在假设我们想要改变这个程序的行为。在我们的例子中,我们不仅仅只是输出每个文件的文件名,还输出了更多信息。在这个例子中,-l 被称为可选参数。

    • 这是一段帮助文档的文字。它是非常有用的,因为当你遇到一个你从未使用过的程序时,你可以通过阅读它的帮助文档来弄清楚它是如何运行的。

    基础

    让我们从一个简单到(几乎)什么也做不了的例子开始:

    1. import argparse
    2. parser = argparse.ArgumentParser()
    3. parser.parse_args()

    以下是该代码的运行结果:

    1. $ python3 prog.py
    2. $ python3 prog.py --help
    3. usage: prog.py [-h]
    4. optional arguments:
    5. -h, --help show this help message and exit
    6. $ python3 prog.py --verbose
    7. usage: prog.py [-h]
    8. prog.py: error: unrecognized arguments: --verbose
    9. $ python3 prog.py foo
    10. usage: prog.py [-h]
    11. prog.py: error: unrecognized arguments: foo

    程序运行情况如下:

    • 在没有任何选项的情况下运行脚本不会在标准输出显示任何内容。这没有什么用处。

    • 第二行代码开始展现出 argparse 模块的作用。我们几乎什么也没有做,但已经得到一条很好的帮助信息。

    • --help 选项,也可缩写为 -h,是唯一一个可以直接使用的选项(即不需要指定该选项的内容)。指定任何内容都会导致错误。即便如此,我们也能直接得到一条有用的用法信息。

    举个例子:

    1. import argparse
    2. parser = argparse.ArgumentParser()
    3. parser.add_argument("echo")
    4. args = parser.parse_args()
    5. print(args.echo)

    运行此程序:

    1. $ python3 prog.py
    2. usage: prog.py [-h] echo
    3. prog.py: error: the following arguments are required: echo
    4. $ python3 prog.py --help
    5. usage: prog.py [-h] echo
    6. positional arguments:
    7. echo
    8. optional arguments:
    9. -h, --help show this help message and exit
    10. $ python3 prog.py foo
    11. foo

    程序运行情况如下:

    • 我们增加了 add_argument() 方法,该方法用于指定程序能够接受哪些命令行选项。在这个例子中,我将选项命名为 echo,与其功能一致。

    • 现在调用我们的程序必须要指定一个选项。

    • The parse_args() method actually returns some data from the options specified, in this case, echo.

    • 这一变量是 免费施放的某种 “魔法”(即是说,不需要指定哪个变量是存储哪个值的)。你也可以注意到,这一名称与传递给方法的字符串参数一致,都是 echo

    然而请注意,尽管显示的帮助看起来清楚完整,但它可以比现在更有帮助。比如我们可以知道 echo 是一个位置参数,但我们除了靠猜或者看源代码,没法知道它是用来干什么的。所以,我们可以把它改造得更有用:

    1. import argparse
    2. parser = argparse.ArgumentParser()
    3. parser.add_argument("echo", help="echo the string you use here")
    4. args = parser.parse_args()
    5. print(args.echo)

    然后我们得到:

    1. $ python3 prog.py -h
    2. usage: prog.py [-h] echo
    3. positional arguments:
    4. echo echo the string you use here
    5. optional arguments:
    6. -h, --help show this help message and exit

    现在,做一些更有用的事情怎么样:

    1. import argparse
    2. parser = argparse.ArgumentParser()
    3. parser.add_argument("square", help="display a square of a given number")
    4. args = parser.parse_args()
    5. print(args.square**2)
    1. $ python3 prog.py 4
    2. Traceback (most recent call last):
    3. File "prog.py", line 5, in <module>
    4. print(args.square**2)
    5. TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'int'

    进展不太顺利。那是因为 argparse 会把我们传递给它的选项视作为字符串,除非我们告诉它别这样。所以,让我们来告诉 来把这一输入视为整数:

    1. import argparse
    2. parser = argparse.ArgumentParser()
    3. parser.add_argument("square", help="display a square of a given number",
    4. type=int)
    5. args = parser.parse_args()
    6. print(args.square**2)

    以下是该代码的运行结果:

    1. $ python3 prog.py 4
    2. 16
    3. $ python3 prog.py four
    4. usage: prog.py [-h] square
    5. prog.py: error: argument square: invalid int value: 'four'

    做得不错。当这个程序在收到错误的无效的输入时,它甚至能在执行计算之前先退出,还能显示很有帮助的错误信息。

    可选参数介绍

    到目前为止,我们一直在研究位置参数。让我们看看如何添加可选的:

    1. import argparse
    2. parser = argparse.ArgumentParser()
    3. parser.add_argument("--verbosity", help="increase output verbosity")
    4. args = parser.parse_args()
    5. if args.verbosity:
    6. print("verbosity turned on")

    和输出:

    程序运行情况如下:

    • 这一程序被设计为当指定 --verbosity 参数时显示某些东西,否则不显示。

    • 不添加这一选项时程序没有提示任何错误而退出,表明这一选项确实是可选的。注意,如果一个可选参数没有被使用时,相关变量被赋值为 None,在此例中是 args.verbosity,这也就是为什么它在 语句中被当作逻辑假。

    • 帮助信息有点不同。

    • 使用 --verbosity 选项时,必须指定一些值(任何值)。

    上述例子接受任何整数值作为 --verbosity 的参数,但对于我们的简单程序而言,只有两个值有实际意义:True 或者 False。让我们据此修改代码:

    1. import argparse
    2. parser = argparse.ArgumentParser()
    3. parser.add_argument("--verbose", help="increase output verbosity",
    4. action="store_true")
    5. args = parser.parse_args()
    6. if args.verbose:
    7. print("verbosity turned on")

    和输出:

    1. $ python3 prog.py --verbose
    2. verbosity turned on
    3. $ python3 prog.py --verbose 1
    4. usage: prog.py [-h] [--verbose]
    5. prog.py: error: unrecognized arguments: 1
    6. $ python3 prog.py --help
    7. usage: prog.py [-h] [--verbose]
    8. optional arguments:
    9. -h, --help show this help message and exit
    10. --verbose increase output verbosity

    程序运行情况如下:

    • 现在,这一选项更多地是一个标志,而非需要接受一个值的什么东西。我们甚至改变了选项的名字来符合这一思路。注意我们现在指定了一个新的关键词 action,并赋值为 "store_true"。这意味着,当这一选项存在时,为 args.verbose 赋值为 True。没有指定时则隐含地赋值为 False

    • 当你为其指定一个值时,它会报错,符合作为标志的真正的精神。

    • 留意不同的帮助文字。

    如果你熟悉命令行的用法,你会发现我还没讲到这一选项的短版本。这也很简单:

    1. import argparse
    2. parser = argparse.ArgumentParser()
    3. parser.add_argument("-v", "--verbose", help="increase output verbosity",
    4. args = parser.parse_args()
    5. if args.verbose:
    6. print("verbosity turned on")

    效果就像这样:

    1. verbosity turned on
    2. $ python3 prog.py --help
    3. usage: prog.py [-h] [-v]
    4. optional arguments:
    5. -h, --help show this help message and exit
    6. -v, --verbose increase output verbosity

    注意,这一新的能力也反映在帮助文本里。

    我们的程序变得越来越复杂了:

    1. import argparse
    2. parser = argparse.ArgumentParser()
    3. parser.add_argument("square", type=int,
    4. help="display a square of a given number")
    5. parser.add_argument("-v", "--verbose", action="store_true",
    6. help="increase output verbosity")
    7. args = parser.parse_args()
    8. answer = args.square**2
    9. if args.verbose:
    10. print("the square of {} equals {}".format(args.square, answer))
    11. else:
    12. print(answer)

    接着是输出:

    1. $ python3 prog.py
    2. usage: prog.py [-h] [-v] square
    3. prog.py: error: the following arguments are required: square
    4. $ python3 prog.py 4
    5. 16
    6. $ python3 prog.py 4 --verbose
    7. the square of 4 equals 16
    8. $ python3 prog.py --verbose 4
    9. the square of 4 equals 16
    • 我们带回了一个位置参数,结果发生了报错。

    • 注意顺序无关紧要。

    给我们的程序加上接受多个冗长度的值,然后实际来用用:

    1. import argparse
    2. parser = argparse.ArgumentParser()
    3. parser.add_argument("square", type=int,
    4. help="display a square of a given number")
    5. parser.add_argument("-v", "--verbosity", type=int,
    6. help="increase output verbosity")
    7. args = parser.parse_args()
    8. answer = args.square**2
    9. if args.verbosity == 2:
    10. print("the square of {} equals {}".format(args.square, answer))
    11. elif args.verbosity == 1:
    12. print("{}^2 == {}".format(args.square, answer))
    13. else:
    14. print(answer)

    和输出:

    1. $ python3 prog.py 4
    2. 16
    3. $ python3 prog.py 4 -v
    4. usage: prog.py [-h] [-v VERBOSITY] square
    5. prog.py: error: argument -v/--verbosity: expected one argument
    6. $ python3 prog.py 4 -v 1
    7. 4^2 == 16
    8. $ python3 prog.py 4 -v 2
    9. the square of 4 equals 16
    10. $ python3 prog.py 4 -v 3
    11. 16

    除了最后一个,看上去都不错。最后一个暴露了我们的程序中有一个 bug。我们可以通过限制 --verbosity 选项可以接受的值来修复它:

    1. import argparse
    2. parser = argparse.ArgumentParser()
    3. parser.add_argument("square", type=int,
    4. help="display a square of a given number")
    5. parser.add_argument("-v", "--verbosity", type=int, choices=[0, 1, 2],
    6. help="increase output verbosity")
    7. args = parser.parse_args()
    8. answer = args.square**2
    9. if args.verbosity == 2:
    10. print("the square of {} equals {}".format(args.square, answer))
    11. elif args.verbosity == 1:
    12. print("{}^2 == {}".format(args.square, answer))
    13. else:
    14. print(answer)
    1. $ python3 prog.py 4 -v 3
    2. usage: prog.py [-h] [-v {0,1,2}] square
    3. prog.py: error: argument -v/--verbosity: invalid choice: 3 (choose from 0, 1, 2)
    4. $ python3 prog.py 4 -h
    5. usage: prog.py [-h] [-v {0,1,2}] square
    6. positional arguments:
    7. square display a square of a given number
    8. optional arguments:
    9. -h, --help show this help message and exit
    10. -v {0,1,2}, --verbosity {0,1,2}
    11. increase output verbosity

    注意这一改变同时反应在错误信息和帮助信息里。

    现在,让我们使用另一种的方式来改变冗长度。这种方式更常见,也和 CPython 的可执行文件处理它自己的冗长度参数的方式一致(参考 python --help 的输出):

    1. import argparse
    2. parser = argparse.ArgumentParser()
    3. parser.add_argument("square", type=int,
    4. help="display the square of a given number")
    5. parser.add_argument("-v", "--verbosity", action="count",
    6. help="increase output verbosity")
    7. args = parser.parse_args()
    8. answer = args.square**2
    9. if args.verbosity == 2:
    10. print("the square of {} equals {}".format(args.square, answer))
    11. elif args.verbosity == 1:
    12. print("{}^2 == {}".format(args.square, answer))
    13. else:
    14. print(answer)

    我们引入了另一种动作 count,来数某一个可选参数出现了几次:

    • 是的,它现在比之前更像是一个标志(和 action="store_true" 相似)。这能解释它为什么报错。

    • 它也表现得与 “store_true” 的行为相似。

    • 这给出了一个关于 count 动作的效果的演示。你之前很可能应该已经看过这种用法。

    • 如果你不添加 -v 标志,这一标志的值会是 None

    • 如期望的那样,添加该标志的长形态能够获得相同的输出。

    • 可惜的是,对于我们的脚本获得的新能力,我们的帮助输出并没有提供很多信息,但我们总是可以通过改善文档来修复这一问题(比如通过 help 关键字参数)。

    • 最后一个输出暴露了我们程序中的一个 bug。

    让我们修复一下:

    1. import argparse
    2. parser = argparse.ArgumentParser()
    3. parser.add_argument("square", type=int,
    4. help="display a square of a given number")
    5. parser.add_argument("-v", "--verbosity", action="count",
    6. help="increase output verbosity")
    7. args = parser.parse_args()
    8. answer = args.square**2
    9. # bugfix: replace == with >=
    10. if args.verbosity >= 2:
    11. print("the square of {} equals {}".format(args.square, answer))
    12. elif args.verbosity >= 1:
    13. print("{}^2 == {}".format(args.square, answer))
    14. print(answer)

    这是它给我们的输出:

    1. $ python3 prog.py 4 -vvv
    2. the square of 4 equals 16
    3. $ python3 prog.py 4 -vvvv
    4. $ python3 prog.py 4
    5. Traceback (most recent call last):
    6. File "prog.py", line 11, in <module>
    7. if args.verbosity >= 2:
    8. TypeError: '>=' not supported between instances of 'NoneType' and 'int'
    • 第一组输出很好,修复了之前的 bug。也就是说,我们希望任何 >= 2 的值尽可能详尽。

    • 第三组输出并不理想。

    让我们修复那个 bug:

    1. import argparse
    2. parser = argparse.ArgumentParser()
    3. parser.add_argument("square", type=int,
    4. help="display a square of a given number")
    5. parser.add_argument("-v", "--verbosity", action="count", default=0,
    6. help="increase output verbosity")
    7. args = parser.parse_args()
    8. answer = args.square**2
    9. if args.verbosity >= 2:
    10. print("the square of {} equals {}".format(args.square, answer))
    11. elif args.verbosity >= 1:
    12. print("{}^2 == {}".format(args.square, answer))
    13. else:
    14. print(answer)

    我们刚刚引入了又一个新的关键字 default。我们把它设置为 0 来让它可以与其他整数值相互比较。记住,默认情况下如果一个可选参数没有被指定,它的值会是 None,并且它不能和整数值相比较(所以产生了 TypeError 异常)。

    然后:

    1. $ python3 prog.py 4
    2. 16

    凭借我们目前已学的东西你就可以做到许多事情,而我们还仅仅学了一些皮毛而已。 模块是非常强大的,在结束篇教程之前我们将再探索更多一些内容。

    进行一些小小的改进

    如果我们想扩展我们的简短程序来执行其他幂次的运算,而不仅是乘方:

    1. import argparse
    2. parser = argparse.ArgumentParser()
    3. parser.add_argument("x", type=int, help="the base")
    4. parser.add_argument("y", type=int, help="the exponent")
    5. parser.add_argument("-v", "--verbosity", action="count", default=0)
    6. args = parser.parse_args()
    7. answer = args.x**args.y
    8. if args.verbosity >= 2:
    9. print("{} to the power {} equals {}".format(args.x, args.y, answer))
    10. elif args.verbosity >= 1:
    11. print("{}^{} == {}".format(args.x, args.y, answer))
    12. else:
    13. print(answer)

    输出:

    1. $ python3 prog.py
    2. usage: prog.py [-h] [-v] x y
    3. prog.py: error: the following arguments are required: x, y
    4. $ python3 prog.py -h
    5. usage: prog.py [-h] [-v] x y
    6. positional arguments:
    7. x the base
    8. y the exponent
    9. optional arguments:
    10. -h, --help show this help message and exit
    11. -v, --verbosity
    12. $ python3 prog.py 4 2 -v
    13. 4^2 == 16

    请注意到目前为止我们一直在使用详细级别来 更改 所显示的文本。 以下示例则使用详细级别来显示 更多的 文本:

    1. import argparse
    2. parser = argparse.ArgumentParser()
    3. parser.add_argument("x", type=int, help="the base")
    4. parser.add_argument("y", type=int, help="the exponent")
    5. parser.add_argument("-v", "--verbosity", action="count", default=0)
    6. args = parser.parse_args()
    7. answer = args.x**args.y
    8. if args.verbosity >= 2:
    9. print("Running '{}'".format(__file__))
    10. if args.verbosity >= 1:
    11. print("{}^{} == ".format(args.x, args.y), end="")
    12. print(answer)

    输出:

    1. $ python3 prog.py 4 2
    2. 16
    3. $ python3 prog.py 4 2 -v
    4. 4^2 == 16
    5. $ python3 prog.py 4 2 -vv
    6. Running 'prog.py'
    7. 4^2 == 16

    矛盾的选项

    到目前为止,我们一直在使用 argparse.ArgumentParser 实例的两个方法。 让我们再介绍第三个方法 add_mutually_exclusive_group()。 它允许我们指定彼此相互冲突的选项。 让我们再更改程序的其余部分以便使用新功能更有意义:我们将引入 --quiet 选项,它将与 --verbose 正好相反:

    1. import argparse
    2. parser = argparse.ArgumentParser()
    3. group = parser.add_mutually_exclusive_group()
    4. group.add_argument("-v", "--verbose", action="store_true")
    5. group.add_argument("-q", "--quiet", action="store_true")
    6. parser.add_argument("x", type=int, help="the base")
    7. parser.add_argument("y", type=int, help="the exponent")
    8. args = parser.parse_args()
    9. answer = args.x**args.y
    10. if args.quiet:
    11. print(answer)
    12. elif args.verbose:
    13. print("{} to the power {} equals {}".format(args.x, args.y, answer))
    14. else:
    15. print("{}^{} == {}".format(args.x, args.y, answer))

    我们的程序现在变得更简洁了,我们出于演示需要略去了一些功能。 无论如何,输出是这样的:

    1. $ python3 prog.py 4 2
    2. 4^2 == 16
    3. $ python3 prog.py 4 2 -q
    4. 16
    5. $ python3 prog.py 4 2 -v
    6. 4 to the power 2 equals 16
    7. $ python3 prog.py 4 2 -vq
    8. usage: prog.py [-h] [-v | -q] x y
    9. prog.py: error: argument -q/--quiet: not allowed with argument -v/--verbose
    10. $ python3 prog.py 4 2 -v --quiet
    11. usage: prog.py [-h] [-v | -q] x y
    12. prog.py: error: argument -q/--quiet: not allowed with argument -v/--verbose

    这应该很容易理解。 我添加了末尾的输出这样你就可以看到其所达到的灵活性,即混合使用长和短两种形式的选项。

    在我们收尾之前,你也许希望告诉你的用户这个程序的主要目标,以免他们还不清楚:

    1. import argparse
    2. parser = argparse.ArgumentParser(description="calculate X to the power of Y")
    3. group = parser.add_mutually_exclusive_group()
    4. group.add_argument("-v", "--verbose", action="store_true")
    5. group.add_argument("-q", "--quiet", action="store_true")
    6. parser.add_argument("x", type=int, help="the base")
    7. parser.add_argument("y", type=int, help="the exponent")
    8. args = parser.parse_args()
    9. answer = args.x**args.y
    10. if args.quiet:
    11. print(answer)
    12. elif args.verbose:
    13. print("{} to the power {} equals {}".format(args.x, args.y, answer))
    14. else:
    15. print("{}^{} == {}".format(args.x, args.y, answer))

    除了这里显示的内容, 模块还提供了更多功能。 它的文档相当详细和完整,包含大量示例。 完成这个教程之后,你应该能毫不困难地阅读该文档。