此教程倾向于大略的介绍下argparse模块,此模块被python标准库推荐用于解析命令行参数。此次编写针对于python3的版本。和python2.x稍微有所区别,尤其在异常消息这块,python3.x有了很大的改进。


注意:还有另外两个模块也起到同样的作用,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()

以下是运行此代码产生的结果:

$ python prog.py
$ python prog.py --help
usage: prog.py [-h]
optional arguments:
  -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

这发生了以下内容:

  • 不带可选信息运行此脚本,不会在标准输出中产生任何结果,没什么意思。

  • 第二次执行体现出了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
optional arguments:
  -h, --help  show this help message and exit
$ python prog.py foo
foo

这发生了以下内容:

  • 我们增加了add_argument()方法,它是用来指定哪些命令行参数程序可以接受。在这个例子中,我们给参数起名叫做echo,同时它的名字也反映出他的功能。

  • 现在,调用我们的程序时,就需要指定一个选项。

  • parse_args()方式返回各选项被指定的数据,在我们这个例子中,是echo。

  • 这里的变量应用了一些“魔法”,argparse表现得非常方便(例如,无需指定此变量存放什么样的值)。你将注意到,此变量的名称与在add_argument()方法中给定的字符串参数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
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

执行结果如下:

$ 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]
optional arguments:
  -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]
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"

执行如下:

$ python prog.py -v
verbosity turned on
$ python 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

执行如下:

$ 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
  • 我们制作了一个位置参数,故程序产生了错误。

  • 注意,位置顺序是没有关系的。

我们将以上的功能增加多种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,
                    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

执行如下:

$ 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

其他看起来都很好,但最后一行产生了一个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

执行如下:

$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
optional arguments:
  -h, --help            show this help message and exit
  -v {0,1,2}, --verbosity {0,1,2}
                        increase output verbosity

注意这种变化也反映在帮助信息中。

现在,让我们用一种不同的方式来使用verbosity,这种用法也很常见。这也是CPython执行程序处理自身verbosity参数的方法(参见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

我们介绍另一个action,"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
optional arguments:
  -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参数)。

  • 最后一个输出展示了一个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

执行如下:

$ 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: unorderable types: NoneType() >= int()
  • 第一的输出执行的很好,我们修复了之前的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异常)。

同时:

$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 "{} to the power {} equals {}".format(args.x, args.y, answer)
elif args.verbosity >= 1:
    print "{}^{} == {}".format(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
optional arguments:
  -h, --help       show this help message and exit
  -v, --verbosity
$ python prog.py 4 2 -v
4^2 == 16

注意到当前我们利用verbosity的等级来修改输出信息的变化。下面的例子更换了使用verbosity的方法来输出更多的信息:

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),
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



冲突选项

当前,我们只利用了argparse.ArgumentParser实例的两个方法。让我们再介绍第三个方法,add_mutually_exclusive_group()。它允许我们指定互相冲突的选项。让我们修改一下程序,使我们添加的新功能更加的合理:我们增加一个--quite选项用来于--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)

我们的程序现在很简单了,我们去掉了一些功能方便描述。执行如下:

$ 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 "{} to the power {} equals {}".format(args.x, args.y, answer)
else:
    print "{}^{} == {}".format(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
optional arguments:
  -h, --help     show this help message and exit
  -v, --verbose
  -q, --quiet



结束语

除了我们在此展示的功能外,argparse模块还提供了很多其他的功能。它对应的文档包含了所有的细节描述和例子。通过本教程的引导,你可以更加好的对此进行消化,而不会感觉非常的吃力。