半小时掌握 Python argparse

只愿长相守 提交于 2020-08-12 07:21:29

argparse 是 Python 的一个库,用于为程序提供命令行接口(Command Line Interface)。网上关于它的教程有很多,本文主要是对 How to Build Command Line Interfaces in Python With argparse 的选择性翻译+个人注解。这篇文章之所以好,首先是因为它可读性强,作者每介绍一个特性,必配合一段代码;其次是因为它全面,读完之后基本可以掌握 argparse 的主要功能。闲言少叙,书归正传。

为什么要用 argparse

Linux 命令行中,ls path_to_be_listed 可以列出目标路径下的文件/文件夹。我们不妨写一个 myls.py ,用 python 实现这个功能。假如不用 argparse 库,下面是一种可能的实现方式:

import os
import sys

if len(sys.argv)>2:
    print("You have specified too many arguments")
    sys.exit()

if len(sys.argv)<2:
    print("You need to specify the path to be listed")

input_path = sys.argv[1] # sys.argv[0] 是当前代码的文件名,i.e. "./myls.py"

if not os.path.isdir(input_path):
    print("The path specified doesn't exist")
    sys.exit()

print('\n'.join(os.listdir(input_path)))

然后就可以在命令行中调用它了:

$ python myls.py
You need to specify the path to be listed

$ python myls.py /mnt /proc /dev
You have specified too many arguments

$ python myls.py /mnt
dir1
dir2

上面的代码中,sys.argv 保存了一个list,其中依次包含了 python 代码的文件名,以及传入的参数。但是它花了大量逻辑处理传入的参数,传入的参数是多了还是少了?是不是有效参数?等等,全靠程序员分析各种可能情况手打错误提示,这样很浪费时间精力。而 argparse 库,则可以替程序员干这些“杂活”。

接下来看 argparse 是如何处理传入的命令行参数的。

使用这个库主要分为四步:

  • import argparse
  • 创建 parser
  • 向 parser 添加位置变量和可选变量
  • 运行 .parse_args()

注意这四步缺一不可,比如在后文的代码示例中,如果只创建了 parser,而没有后面两步,运行 help 时将不会打印帮助文档。

执行完 .parse_args() 后,会得到一个 Namespace object,里面包含了从命令行中接收到的参数信息。

代码如下,其中细节不用纠结,后文会详细介绍。

#1) Import the argparse library
import argparse

import os

#2) Create the parser
my_parser = argparse.ArgumentParser(description='List the content of a folder')

#3) Add the arguments
my_parser.add_argument('Path', metavar='path', type=str, help='the path to list')

#4) Execute the parse_args() method
args = my_parser.parse_args() # Namespace object

input_path = args.Path

if not os.path.isdir(input_path):
    print('The path specified does not exist')
    sys.exit()

print('\n'.join(os.listdir(input_path)))

在命令行中再次调用上述代码:

$ python myls.py
usage: myls.py [-h] path
myls.py: error: the following arguments are required: path

我们发现,不用程序员在代码中写判断语句,argparse 库自动就检测出调用时没有指定 path 参数并准确报错。同时,在 usage 行,还提示了可以选择设置 -h flag 查看帮助。

$ python myls.py -h
usage: myls.py [-h] path

List the content of a folder

positional arguments:
  path        the path to list

optional arguments:
  -h, --help  show this help message and exit

打印出来的帮助信息中包含了该函数的调用方法,函数的用途,位置参数(必须设置的)、可选变量等等。

本节只是展示了 argparse 的部分功能,下面将详细介绍如何使用 argparse 函数库。

1. 设置程序的名字

默认情况下,argparse 函数库把 sys.argv[0] (即当前被执行代码的文件名)设置为程序的名字(主要用在 help 文档中), 但是用户也可以在创建 parser 时,通过设置 prog 参数自定义程序名。把上一节 argparse 代码中 Create the parser 部分修改如下:

# Create the parser
my_parser = argparse.ArgumentParser(prog='fuckfuck',
                                    description='List the content of a folder') 

在命令行调用:

$ python myls.py
usage: fuckfuck [-h] path
fuckfuck: error: the following arguments are required: path

发现程序的名字从原来默认的 myls.py 被修改为了 fuckfuck。

2. 设置 Usage Help

上文中我们通过 python myls.py -h 可以代码的帮助文档,文档的第一行即展示了代码的调用方法:usage: myls.py [-h] path(或者是我们自定义的程序名:usage: fuckfuck [-h] path)。我们也可以自定义这一行展示的内容。再次修改原代码中 Create the parser 部分:

# Create the parser
my_parser = argparse.ArgumentParser(prog='fuckfuck', 
                                    usage='%(prog)s [options] path ffffffffffffff',
                                    description='List the content of a folder')

注意在运行时 %(prog)s 将被我们设置的程序名(i.e. fuckfuck)替换。命令行调用(为了节省篇幅,不加 -h ,而是调用时不传入参数,让它报错,一样可以显示出我们修改的这部分代码对程序的影响):

$ python myls.py
usage: fuckfuck [options] path ffffffffffffff
fuckfuck: error: the following arguments are required: path

3. 设置 help 中开头和末尾的文字

通过设置 descriptionepilog 可以自定义帮助文档开头和末尾部分的文字。再次修改原代码中 Create the parser 部分:

# Create the parser
my_parser = argparse.ArgumentParser(description='List the content of a folder', 
                                    epilog='Enjoy the program! :)')

命令行调用帮助文档:

usage: myls.py [-h] path

List the content of a folder <<<<<<<<<<<<<<<<-------------------这里

positional arguments:
  path        the path to list

optional arguments:
  -h, --help  show this help message and exit

Enjoy the program! :) <<<<<<<<<<<<<<<---------------------------这里

4. 自定义前缀字符

默认情况下,命令行运行代码时,传入的可选变量(optional arguments)要用短线 -作为前缀字符(prefix chars)。这个前缀字符也可以通过设置 prefix_chars 自定义:

my_parser = argparse.ArgumentParser(description='List the content of a floder',
                                    epilog='Enjoy the program! :)',
                                    prefix_chars='/')

修改后,我们不能再用 -h 调用帮助文档了,而是要用/h

$ python .\myls.py /h
usage: prog.py [/h] path <<<<<<<<<<<<<----------------------这里

List the content of a floder

positional arguments:
  path        the path to list

optional arguments:
  /h, //help  show this help message and exit <<<<<<<<<<<<<----------------------这里

Enjoy the program! :)

帮助文档中的短线都改成了我们指定的字符('/')。

5. 设置外部参数文件的前缀字符

当代码的命令行参数非常长时,把它们预先存放到外部文件中,运行程序时直接加载该文件是个好主意。argparse 通过设置 from_file_prefix_chars支持这一操作。

# fromfile_example.py
import argparse

my_parser = argparse.ArgumentParser(fromfile_prefix_chars='@')

my_parser.add_argument('a', help='a first argument')
my_parser.add_argument('b', help='a second argument')
my_parser.add_argument('c', help='a third argument')
my_parser.add_argument('d', help='a fourth argument')
my_parser.add_argument('e', help='a fifth argument')
my_parser.add_argument('-v', '--verbose', action='store_true', help='an optional argument')

args = my_parser.parse_args()
print('If you read this line it means that you have provided all the parameters')

如果什么都不传入,直接在命令行运行该段代码,会产生如下错误提示:

$ python .\fromfile_example.py
usage: fromfile_example.py [-h] [-v] a b c d e
fromfile_example.py: error: the following arguments are required: a, b, c, d, e

Python 的 argparse 函数库在抱怨你没有提供足够的变量。

现在我们创建一个 args.txt,包含所有必要的参数,每个参数占一行,如下所示:

first
second
third
fourth
fifth

命令行调用:

$ python fromfile_example.py @args.txt
If you read this line it means that you have provided all the parameters

需要补充的是,在 windows 的 cmd 中,@ 有特殊含义,需要把 @args.txt 外面加上单引号或者双引号。此外不一定非得设置成 @fromfile_prefix_chars 可以设置成各种字符,包括二十六个英文字母,百分号,美元符号等等。此外,args.txt 里面的参数不能多也不能少,否则都会报错。

6. 允许/禁止 缩写

研究下面的代码,它将把传入的 --input 变量打印出来:

# abbrev_example.py
import argparse

my_parser = argparse.ArgumentParser()
my_parser.add_argument('--input', action='store', type=int, required=True)
my_parser.add_argument('--id', action='store', type=int)

args = my_parser.parse_args()
print(args.input)

这里面涉及了用两个短线引导的可选变量(optional argument),它和不用短线引导的位置变量(positional argument)的区别是,想在命令行传入该变量,需要显式指定变量名(正是由于需要显式指定变量名,才牵扯出来了本节变量名缩写的问题,位置变量不存在变量名缩写的问题):

$ python .\abbrev_example.py --input 10 --id 20
10

argparse 提供了参数缩写功能,即每次从命令行传入可选变量时,不需要完整输入名称:

$ python .\abbrev_example.py --inpu 10
10

$ python .\abbrev_example.py --inp 10
10

$ python .\abbrev_example.py --in 10
10

$ python .\abbrev_example.py --i 10
usage: abbrev_example.py [-h] --input INPUT [--id ID]
abbrev_example.py: error: ambiguous option: --i could match --input, --id

除了最后一个调用,原因是把变量名缩减到 --i 时,--input--id 会混淆。

如果你不喜欢 argparse 的这个行为,想强制调用者在命令行输入完整的变量名,可以在初始化 parser 时设置:allow_abbrev=False

# abbrev_example.py
import argparse

my_parser = argparse.ArgumentParser(allow_abbrev=False)
my_parser.add_argument('--input', action='store', type=int, required=True)

args = my_parser.parse_args()
print(args.input)

这时再在命令行输入缩写的变量名,则会报如下错误:

$ python .\abbrev_example.py --inpu 10
usage: abbrev_example.py [-h] --input INPUT
abbrev_example.py: error: the following arguments are required: --input

说明禁止缩写后, argparse 并没有认出传入的 --inpu 参数。

7. 使用 Auto Help

上文中经常用到传入 -h 参数获取argparse自动生成的帮助文档,这个 feature 也是可以被禁止的,把上面代码的 create the parser 句加入 add_help=False

# Create the parser
my_parser = argparse.ArgumentParser(allow_abbrev=False, add_help=False)

命令行调用 -h

$ python .\abbrev_example.py -h
usage: abbrev_example.py --input INPUT
abbrev_example.py: error: the following arguments are required: --input

-h 参数将不被接收。

8. 设置变量的名字或者 Flag

命令行接口支持两种类型的变量:

  • Positional arguments 位置变量
  • Optional arguments 可选变量

位置变量是用户必须设置的变量,例如 ls 命令的 path 参数,不设置就没法运行。它之所以叫位置变量,是因为它们在命令中传入的位置不能错,必须按照代码中加入 parser 的先后顺序传入。例如 Linux 命令行的复制命令:cp [OPTION]...[-T] SOURCE DEST,这里的 SOURCE 和 DEST 不能颠倒,必须按顺序传入。

可选变量对于代码运行不是必须的,但是设置后可以改变程序的行为,例如对 cp 命令,-r 就是可选变量,设置后可以迭代地复制整个文件夹。 在parser.add_argument()时,可选变量必须有一条或两条短线 - 作为前缀,而位置变量则没有。一般把一条短线跟一个字母作为可选变量的缩写,方便命令行传入参数。

代码示例:

# myls.py
# Import the argparse library
import argparse

import os
import sys

# Create the parser
my_parser = argparse.ArgumentParser(description='List the content of a folder')

# Add the arguments
my_parser.add_argument('Path',
                       metavar='path',
                       type=str,
                       help='the path to list')
my_parser.add_argument('-l',
                       '--long',
                       action='store_true',
                       help='enable the long listing format')

# Execute parse_args()
args = my_parser.parse_args()

input_path = args.Path

if not os.path.isdir(input_path):
    print('The path specified does not exist')
    sys.exit()

for line in os.listdir(input_path):
    if args.long:  # Simplified long listing
        size = os.stat(os.path.join(input_path, line)).st_size
        line = '%10d  %s' % (size, line)
    print(line)

执行代码,我们发现 -l 选项被 argparse 接受了:

$ python myls.py -h
usage: myls.py [-h] [-l] path

List the content of a folder

positional arguments:
path        the path to list

optional arguments:
-h, --help  show this help message and exit
-l, --long  enable the long listing format

设置变量的 action 参数

对可选变量可以设置 action 参数。这个参数可以设置为如下值:

  • store 将输入的值保存到 Namespace 实例中。(这是默认操作)
  • store_const 当该可选变量在命令行被设置时,保存一个常数(需要用 const 指定,否则报错)。
  • store_true 当在命令行设置该可选变量时,将会给它绑定一个布尔值 True,若不设置,则该可选变量默认绑定 False
  • store_falsestore_true 表现相反,不设置绑定的是 True,显式设定后绑定 False
  • append 保存一个列表,每次设置这个变量,都会为列表添加一个值。
  • append_const 每次设置时给列表添加一个常量。
  • count保存一个整数,整数的值为该参数被设置的次数。
  • help 显示帮助文档并退出
  • version 显示程序版本并推出

如下代码同时测试上面的所有选项:

# actions_example.py
import argparse

my_parser = argparse.ArgumentParser()
my_parser.version = '1.0'
my_parser.add_argument('-a', action='store')
my_parser.add_argument('-b', action='store_const', const=42)
my_parser.add_argument('-c', action='store_true')
my_parser.add_argument('-d', action='store_false')
my_parser.add_argument('-e', action='append')
my_parser.add_argument('-f', action='append_const', const=42)
my_parser.add_argument('-g', action='count')
my_parser.add_argument('-i', action='help')
my_parser.add_argument('-j', action='version')

args = my_parser.parse_args()

print(vars(args))

该代码将每种 action 分配给一个 optional argument,然后打印出它们从命令行捕获的值。

首先,我们调用时不显式设置任何变量,观察它们的默认值:

$ python actions_example.py
{'a': None, 'b': None, 'c': False, 'd': True, 'e': None, 'f': None, 'g': None}

上述大多数变量的默认值均为 None,除了绑定了 'store_true'store_false 的变量。不在命令行显式设定它们时,它们默认存储的是和 action 相反的 False 和 True(这背后的逻辑是,让存 True 时才存 True,不让存时默认存 False,反之亦然)。

对于设置了 action='store' 的变量,它将无脑存储命令行传入的参数,不做任何修改:

$ python actions_example.py -a 42 <<<<--------- 只设置 a,其余变量保持默认状态
{'a': '42', 'b': None, 'c': False, 'd': True, 'e': None, 'f': None, 'g': None}

$ python actions_example.py -a "test"
{'a': 'test', 'b': None, 'c': False, 'd': True, 'e': None, 'f': None, 'g': None}

可以看出,命令行传入的参数被保存为字符或者字符串。

store_const 会保存一个事先绑定的常量,它不接受多余的参数,只需要变量名在命令行中出现:

$ python actions_example.py -b <<<<--------- 只设置 b,其余变量保持默认状态
{'a': None, 'b': 42, 'c': False, 'd': True, 'e': None, 'f': None, 'g': None}

store_truestore_false

$ python actions_example.py
{'a': None, 'b': None, 'c': False, 'd': True, 'e': None, 'f': None, 'g': None}
$ python actions_example.py -c
{'a': None, 'b': None, 'c': True, 'd': True, 'e': None, 'f': None, 'g': None}
$ python actions_example.py -d
{'a': None, 'b': None, 'c': False, 'd': False, 'e': None, 'f': None, 'g': None}

append,变量可以在命令行中多次指定值,每次的值都会存到列表中:

$ python actions_example.py -e me -e you -e us
{'a': None, 'b': None, 'c': False, 'd': True, 'e': ['me', 'you', 'us'], 'f': None, 'g': None}

append_constappend差不多,只不过不接受变量参数,每次调用变量名会给列表保存一个常量:

$ python actions_example.py -f -f <<<<<<------这里也可以写成 -ff
{'a': None, 'b': None, 'c': False, 'd': True, 'e': None, 'f': [42, 42], 'g': None}

count 用于计算该变量被传入了多少次,一般用它来设置输出的(冗余度)verbosity。例如想实现类似-v 的输出比 -vvv 的输出更简洁的功能。

$ python actions_example.py -ggg
{'a': None, 'b': None, 'c': False, 'd': True, 'e': None, 'f': None, 'g': 3}
$ python actions_example.py -ggggg
{'a': None, 'b': None, 'c': False, 'd': True, 'e': None, 'f': None, 'g': 5}

help action 已经多次见过了,默认用 -h 调用 help,不过也可以给它绑定别的名字,本例中的 -i 也可以。

version,调用了绑定 version 的变量,会输出代码中设置的 my_parser.version

除了上文中 argparse 预定义的 action,还可以自定义 action:

# custom_action.py
import argparse

class VerboseStore(argparse.Action): # 自定义类继承自 Action 类
    def __init__(self, option_strings, dest, nargs=None, **kwargs): 
        # 函数签名中包含的参数由 parser.add_argument()传入,其中 option_string 为
        # 变量名, 具体是缩写的 -i 还是全名 --input 由命令行设置的哪个决定,
        # dest 为变量在内存中保存的名字,不设置时和长版的参数名一致 i.e. input .
        # nargs 控制该变量从命令行中读入它后面连续多少个值,设置为 None 时读1个.
        if nargs is not None:
            # 这里不接受 nargs,但实际上完全可以接受
            raise ValueError('nargs not allowed') 
        super(VerboseStore, self).__init__(option_strings, dest, **kwargs)
        # 一旦接受了 nargs, 父类的构造函数中也应该传入 nargs,
        # 通过 inspect.sigature(argparse.Action) 可以看到,它第三个参数就是 nargs,
        # 因此可以直接跟在 dest 后面传入.

    def __call__(self, parser, namespace, values, option_string=None):
        # 调用 parser.parse_args() 时会调用这个方法
        print('Here I am, setting the ' \
              'values %r for the %r option...' % (values, option_string))
        setattr(namespace, self.dest, values) # 把值按照它的名字保存到 Namespace 实例

my_parser = argparse.ArgumentParser()
my_parser.add_argument('-i', '--input', action=VerboseStore, type=int)

args = my_parser.parse_args()

print(vars(args))

上面的代码和 store 干的是一件事,只不过多输出了一段话。

$ python custom_action.py -i 42
Here I am, setting the values 42 for the '-i' option...
{'input': 42}

设置每个变量在命令行读入的值的数目

parser 默认情况下会给每个命令行的变量分配它后面紧跟的那个值,但是可以通过设置 nargs 来让这个变量读入更多的值,例如:

# nargs_example.py
import argparse

my_parser = argparse.ArgumentParser()
my_parser.add_argument('--input', action='store', type=int, nargs=3)

args = my_parser.parse_args()

print(args.input)

设置了 nargs=3 后,该变量就会强制从命令行读入三个值,给不够数目或者给多了会报错:

$ python nargs_example.py --input 42
usage: nargs_example.py [-h] [--input INPUT INPUT INPUT]
nargs_example.py: error: argument --input: expected 3 arguments

$ python nargs_example.py --input 42 42 42
[42, 42, 42]

nargs 除了接受具体的数值,还接受如下符号:

  • ?:读一个值,可以不设置(默认就是读一个)
  • *:读多个值(包括0个),会被收集到一个列表中
  • +:读至少一个值
  • argparse.REMAINDER:把命令行所有剩余的值都收集起来

下面的代码将分别测试这几种 nargs,为了节省篇幅,只写其中一种情况,其余的在注释中。

# nargs_example.py
import argparse

my_parser = argparse.ArgumentParser()
my_parser.add_argument('input',
                       action='store',
                       nargs='?', # '*', '+', argparse.REMAINDER
                       default='my default value')

args = my_parser.parse_args()

print(args.input)

下面综合了各种符号在命令行下的行为:

# 设置 nargs='?'
$ python nargs_example.py 'my custom value' 只吸收一个参数
my custom value

$ python nargs_example.py # 不传入参数时设为 default 值,没有 default 时为 None
my default value

# ---------------------------
# 设置 nargs='*'
$ python nargs_example.py me you us # 命令行有多少参数吸收多少参数
['me', 'you', 'us']

$ python nargs_example.py # 没有参数时为 default, 没有 default 为 None
my default value

# -----------------------------
# 设置 nargs='+'
$ python nargs_example.py me you us # 传入多个参数时,和 * 行为一样
['me', 'you', 'us'] 

$ python nargs_example.py # 不传入参数时报错,不会返回 default 或者 None
usage: nargs_example.py [-h] input [input ...]
nargs_example.py: error: the following arguments are required: input

#---------------------------------
# 设置 nargs=argparse.REMINDER
# 这部分代码和上文略有不同,设置了两个 argument,第一个只接受一个参数,
# 第二个接受剩余参数,具体见原文链接.
$ python nargs_example.py me you us
first = 'me'
others = ['you', 'us']

为没有赋值的变量设置 default

my_parser.add_argument('-a', action='store', default='42')

上文中用过很多次了,略过。

设置变量类型

my_parser.add_argument('-a', action='store', type=int)

很简单,只要在 .add_argument 参数中指定 type 就好了。

设置变量值得选项

my_parser.add_argument('-a', action='store', choices=['head', 'tail'])

.add_argument 参数中指定 choices,然后传入一个列表,列表中包含变量可选值,传入列表以外的值会报错。可以用 choices=range(1, 5) 设置变量可接受的值。

设置变量是否必要

my_parser.add_argument('-a',
                       action='store',
                       choices=['head', 'tail'],
                       required=True)

required 参数只能绑定给 optional argument 上,因此 -a 前面的短线是必要的。此外,设置了 requireddefault 将不起作用。

在帮助手册中展示变量的简介

my_parser.add_argument('-a',
                       action='store',
                       choices=['head', 'tail'],
                       help='set the user choice to head or tail')

只需设置 help='whatever you want to write' 就可以了。

定义互斥的组

有时两个参数是互斥关系,不能同时设置,例如 --verbose--silent。这时就可以把它们加入互斥组。

# groups.py
import argparse

my_parser = argparse.ArgumentParser()
my_group = my_parser.add_mutually_exclusive_group(required=True) # 这一行创建互斥组

my_group.add_argument('-v', '--verbose', action='store_true') # 在互斥组中加变量
my_group.add_argument('-s', '--silent', action='store_true')

args = my_parser.parse_args()

print(vars(args))

如果命令行中同时设置这两个参数,则会报错:

$ python groups.py -v -s
usage: groups.py [-h] (-v | -s)
groups.py: error: argument -s/--silent: not allowed with argument -v/--verbose

在文档中设置变量名

有时候我们希望在帮助文档中展示的变量名和代码中实际的名字不一样(默认帮助文档中的变量名是代码中实际变量名的大写状态),就可以在添加变量时设置 metavar参数。

# metavar_example.py
import argparse

my_parser = argparse.ArgumentParser()
my_parser.add_argument('-v',
                       '--verbosity',
                       action='store',
                       type=int, 
                       metavar='LEVEL')

args = my_parser.parse_args()

print(vars(args))

# -------------------------------------------
命令行
$ python metavar_example.py -h
usage: metavar_example.py [-h] [-v LEVEL] <----------我们发现这里的名字不是 VERBOSITY了

optional arguments:
  -h, --help            show this help message and exit
  -v LEVEL, --verbosity LEVEL

把读入的变量重命名

对于 positional argument .add_argument会把它的第一个参数设置为变量名,对于 optional argument,.add_argument 会把较长的那个字符串设置为变量名。如果名字中有短线-,则将其转化为下划线:verbosity-level->verbosity_level

同时,还可以给 optional argument 在程序中设置另外的名字,即命令行中的名字和参数解析后的名字不同,这里将用到 dest 参数。

# dest_example.py
import argparse

my_parser = argparse.ArgumentParser()
my_parser.add_argument('-v',
                       '--verbosity',
                       action='store',
                       type=int,
                       dest='my_verbosity_level')

args = my_parser.parse_args()

print(vars(args))

# -----------------------------
命令行
我们看到实际保存的变量名为 my_verbosity_level 而不再是 verbosity 
$ python dest_example.py -v 42
{'my_verbosity_level': 42}


总结

本文基本涵盖了 argparse 的常用功能,或许还有遗漏,但是我相信只要掌握了文中的内容,即使遇到新的用法,也能很快从文档中掌握它们。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!