Argparse 教程
*************

作者:
   Tshepang Lekhonkhobe

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

注解:

  还有另外两个模块可以完成同样的任务，称为 "getopt" (对应于 C 语言中的
  "getopt()" 函数) 和被弃用的 "optparse"。还要注意 "argparse" 是基于
  "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()

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

   $ python3 prog.py
   $ python3 prog.py --help
   usage: prog.py [-h]

   optional arguments:
     -h, --help  show this help message and exit
   $ python3 prog.py --verbose
   usage: prog.py [-h]
   prog.py: error: unrecognized arguments: --verbose
   $ python3 prog.py foo
   usage: prog.py [-h]
   prog.py: error: unrecognized arguments: foo

程序运行情况如下：

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

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

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


位置参数介绍
============

举个例子：

   import argparse
   parser = argparse.ArgumentParser()
   parser.add_argument("echo")
   args = parser.parse_args()
   print(args.echo)

运行此程序：

   $ python3 prog.py
   usage: prog.py [-h] echo
   prog.py: error: the following arguments are required: echo
   $ python3 prog.py --help
   usage: prog.py [-h] echo

   positional arguments:
     echo

   optional arguments:
     -h, --help  show this help message and exit
   $ python3 prog.py foo
   foo

程序运行情况如下：

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

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

* The "parse_args()" method actually returns some data from the
  options specified, in this case, "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)

然后我们得到：

   $ python3 prog.py -h
   usage: prog.py [-h] echo

   positional arguments:
     echo        echo the string you use here

   optional arguments:
     -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)

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

   $ python3 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)

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

   $ python3 prog.py 4
   16
   $ python3 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")

和输出：

   $ python3 prog.py --verbosity 1
   verbosity turned on
   $ python3 prog.py
   $ python3 prog.py --help
   usage: prog.py [-h] [--verbosity VERBOSITY]

   optional arguments:
     -h, --help            show this help message and exit
     --verbosity VERBOSITY
                           increase output verbosity
   $ python3 prog.py --verbosity
   usage: prog.py [-h] [--verbosity VERBOSITY]
   prog.py: error: argument --verbosity: expected one argument

程序运行情况如下：

* 这一程序被设计为当指定 "--verbosity" 选项时显示某些东西，否则不显示
  。

* 不添加这一选项时程序没有提示任何错误而退出，表明这一选项确实是可选的
  。注意，如果一个可选参数没有被使用时，相关变量被赋值为 "None"，在此
  例中是 "args.verbosity"，这也就是为什么它在 "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")

和输出：

   $ python3 prog.py --verbose
   verbosity turned on
   $ python3 prog.py --verbose 1
   usage: prog.py [-h] [--verbose]
   prog.py: error: unrecognized arguments: 1
   $ python3 prog.py --help
   usage: prog.py [-h] [--verbose]

   optional arguments:
     -h, --help  show this help message and exit
     --verbose   increase output verbosity

程序运行情况如下：

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

效果就像这样：

   $ python3 prog.py -v
   verbosity turned on
   $ python3 prog.py --help
   usage: prog.py [-h] [-v]

   optional arguments:
     -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("the square of {} equals {}".format(args.square, answer))
   else:
       print(answer)

接着是输出：

   $ python3 prog.py
   usage: prog.py [-h] [-v] square
   prog.py: error: the following arguments are required: square
   $ python3 prog.py 4
   16
   $ python3 prog.py 4 --verbose
   the square of 4 equals 16
   $ python3 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("the square of {} equals {}".format(args.square, answer))
   elif args.verbosity == 1:
       print("{}^2 == {}".format(args.square, answer))
   else:
       print(answer)

和输出：

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

除了最后一个，看上去都不错。最后一个暴露了我们的程序中有一个 bug。我们
可以通过限制 "--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("the square of {} equals {}".format(args.square, answer))
   elif args.verbosity == 1:
       print("{}^2 == {}".format(args.square, answer))
   else:
       print(answer)

和输出：

   $ python3 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)
   $ python3 prog.py 4 -h
   usage: prog.py [-h] [-v {0,1,2}] square

   positional arguments:
     square                display a square of a given number

   optional arguments:
     -h, --help            show this help message and exit
     -v {0,1,2}, --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("the square of {} equals {}".format(args.square, answer))
   elif args.verbosity == 1:
       print("{}^2 == {}".format(args.square, answer))
   else:
       print(answer)

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

   $ python3 prog.py 4
   16
   $ python3 prog.py 4 -v
   4^2 == 16
   $ python3 prog.py 4 -vv
   the square of 4 equals 16
   $ python3 prog.py 4 --verbosity --verbosity
   the square of 4 equals 16
   $ python3 prog.py 4 -v 1
   usage: prog.py [-h] [-v] square
   prog.py: error: unrecognized arguments: 1
   $ python3 prog.py 4 -h
   usage: prog.py [-h] [-v] square

   positional arguments:
     square           display a square of a given number

   optional arguments:
     -h, --help       show this help message and exit
     -v, --verbosity  increase output verbosity
   $ python3 prog.py 4 -vvv
   16

* 是的，它现在比前一版本更像是一个标志（和 "action="store_true"" 相似
  ）。这能解释它为什么报错。

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

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

* 如果你不添加 "-v" 标志，这一标志的值会是 "None"。

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

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

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

让我们修复一下：

   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("the square of {} equals {}".format(args.square, answer))
   elif args.verbosity >= 1:
       print("{}^2 == {}".format(args.square, answer))
   else:
       print(answer)

这是它给我们的输出：

   $ python3 prog.py 4 -vvv
   the square of 4 equals 16
   $ python3 prog.py 4 -vvvv
   the square of 4 equals 16
   $ python3 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'

* 第一组输出很好，修复了之前的 bug。也就是说，我们希望任何 >= 2 的值尽
  可能详尽。

* 第三组输出并不理想。

让我们修复那个 bug：

   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("the square of {} equals {}".format(args.square, answer))
   elif args.verbosity >= 1:
       print("{}^2 == {}".format(args.square, answer))
   else:
       print(answer)

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

然后：

   $ python3 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("{} to the power {} equals {}".format(args.x, args.y, answer))
   elif args.verbosity >= 1:
       print("{}^{} == {}".format(args.x, args.y, answer))
   else:
       print(answer)

输出：

   $ python3 prog.py
   usage: prog.py [-h] [-v] x y
   prog.py: error: the following arguments are required: x, y
   $ python3 prog.py -h
   usage: prog.py [-h] [-v] x y

   positional arguments:
     x                the base
     y                the exponent

   optional arguments:
     -h, --help       show this help message and exit
     -v, --verbosity
   $ python3 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("Running '{}'".format(__file__))
   if args.verbosity >= 1:
       print("{}^{} == ".format(args.x, args.y), end="")
   print(answer)

输出：

   $ python3 prog.py 4 2
   16
   $ python3 prog.py 4 2 -v
   4^2 == 16
   $ python3 prog.py 4 2 -vv
   Running 'prog.py'
   4^2 == 16


矛盾的选项
----------

到目前为止，我们一直在使用 "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("{} to the power {} equals {}".format(args.x, args.y, answer))
   else:
       print("{}^{} == {}".format(args.x, args.y, answer))

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

   $ python3 prog.py 4 2
   4^2 == 16
   $ python3 prog.py 4 2 -q
   16
   $ python3 prog.py 4 2 -v
   4 to the power 2 equals 16
   $ python3 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
   $ python3 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("{} to the power {} equals {}".format(args.x, args.y, answer))
   else:
       print("{}^{} == {}".format(args.x, args.y, answer))

请注意用法文本中有细微的差异。 注意 "[-v | -q]"，它的意思是说我们可以
使用 "-v" 或 "-q"，但不能同时使用两者：

   $ python3 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

   optional arguments:
     -h, --help     show this help message and exit
     -v, --verbose
     -q, --quiet


后记
====

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