python散装笔记——83: 解析命令行参数
大多数命令行工具依赖于程序执行时传递给程序的参数。这些程序不提示输入,而是期望设置数据或特定的标志(变成布尔值)。这使得用户和其他程序都能在 Python 文件启动时通过数据运行它。本节将解释和演示 Python 命令行参数的实现和使用。
1: argparse 中的 Hello world
下面的程序会向用户问好。它需要一个位置参数,即用户名,也可以告诉用户问候语。
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('name', help='name of user')
parser.add_argument('-g', '--greeting',
default='Hello',
help='optional alternate greeting'
)
args = parser.parse_args()
print("{greeting}, {name}!".format(
greeting=args.greeting,
name=args.name)
)
$ python hello.py --help
usage: hello.py [-h] [-g GREETING] name
positional arguments:
name name of user
optional arguments:
-h, --help show this help message and exit
-g GREETING, --greeting GREETING optional alternate greeting
$ python hello.py world
Hello, world!
$ python hello.py John -g Howdy
Howdy, John!

详情请阅读 argparse 文档。
2: 使用 argv 命令行参数
每当从命令行调用 Python 脚本时,用户可以提供额外的命令行参数,这些参数将传递给脚本。程序员可以通过系统变量 sys.argv 获取这些参数(argv 是大多数编程语言的传统名称,意思是 “参数向量”)。
按照惯例,sys.argv 列表中的第一个元素是 Python 脚本本身的名称,而其余元素则是用户在调用脚本时传递的标记。
# cli.py
import sys
print(sys.argv)
$ python3 cli.py
=> ['cli.py']
$ python3 cli.py fizz
=> ['cli.py', 'fizz']
$ python3 cli.py fizz buzz
=> ['cli.py', 'fizz', 'buzz']

下面是另一个如何使用 argv 的例子。我们首先去掉 sys.argv 的初始元素,因为它包含脚本的名称。然后,我们将其余参数合并成一句话,最后打印这句话,并在前面加上当前登录用户的名称(以便模拟聊天程序)。
import getpass
import sys
words = sys.argv[1:]
sentence = " ".join(words)
print("[%s] %s" % (getpass.getuser(), sentence))
在 “手动 ”解析大量非位置参数时,通常使用的算法是遍历 sys.argv 列表。一种方法是遍历该列表并弹出其中的每个元素:
# reverse and copy sys.argv
argv = reversed(sys.argv)
# extract the first element
arg = argv.pop()
# stop iterating when there's no more args to pop()
while len(argv) > 0:
if arg in ('-f', '--foo'):
print('seen foo!')
elif arg in ('-b', '--bar'):
print('seen bar!')
elif arg in ('-a', '--with-arg'):
arg = arg.pop()
print('seen value: {}'.format(arg))
# get the next value
arg = argv.pop()
3: 使用 argparse 设置互斥参数
如果希望两个或多个参数互斥。可以使用函数
argparse.ArgumentParser.add_mutually_exclusive_group()。在下面的示例中,foo 或 bar 可以存在,但不能同时存在。
import argparse
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument("-f", "--foo")
group.add_argument("-b", "--bar")
args = parser.parse_args()
print("foo = ", args.foo)
print ("bar = ", args.bar)

如果尝试运行脚本,同时指定 --foo 和 --bar 参数,脚本将发出以下抱怨信息。
error: argument -b/--bar: not allowed with argument -f/--foo
4: 使用 docopt 的基本示例
docopt 颠覆了命令行参数解析。你只需编写程序的用法字符串,而无需解析参数,docopt 会解析用法字符串,并用它提取命令行参数。
"""
Usage:
script_docopt.py [-a] [-b]
Options:
-a Print all the things.
-b Get more bees into the path.
"""
from docopt import docopt
if __name__ == "__main__":
args = docopt(__doc__)
import pprint; pprint.pprint(args)
运行样本:
$ python script_docopt.py
Usage:
script_docopt.py [-a] [-b]
$ python script_docopt.py something
{'-a': False,
'-b': False,
'': 'something'}
$ python script_docopt.py something -a
{'-a': True,
'-b': False,
'': 'something'}
$ python script_docopt.py -b something -a
{'-a': True,
'-b': True,
'': 'something'}
5: 使用 argparse 自定义解析器错误信息
您可以根据脚本需要创建解析器错误信息。这是通过
argparse.ArgumentParser.error 函数实现的。下面的示例显示了脚本在给出 --foo 而未给出 --bar 时向 stderr 打印用法和错误信息的情况。
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-f", "--foo")
parser.add_argument("-b", "--bar")
args = parser.parse_args()
if args.foo and args.bar is None:
parser.error("--foo requires --bar. You did not specify bar.")
print("foo =", args.foo)
print("bar =", args.bar)
假设脚本名为 sample.py,并运行 python sample.py --foo ds_in_fridge.
这个脚本会发出如下提示:
usage: sample.py [-h] [-f FOO] [-b BAR]
sample.py: error: --foo requires --bar. You did not specify bar.

6: 使用 argparse.add_argument_group() 对参数进行概念分组
当你创建 argparse ArgumentParser() 并使用'-h'运行程序时,你会收到一条自动使用信息,解释你可以使用哪些参数运行软件。默认情况下,位置参数和条件参数被分为两类,例如,下面是一个小脚本(example.py)和运行 python example.py -h 时的输出。
import argparse
parser = argparse.ArgumentParser(description='Simple example')
parser.add_argument('name', help='Who to greet', default='World')
parser.add_argument('--bar_this')
parser.add_argument('--bar_that')
parser.add_argument('--foo_this')
parser.add_argument('--foo_that')
args = parser.parse_args()
usage: example2.py [-h] [--bar_this BAR_THIS] [--bar_that BAR_THAT]
[--foo_this FOO_THIS] [--foo_that FOO_THAT]
name
Simple example
positional arguments:
name Who to greet
optional arguments:
-h, --help show this help message and exit
--bar_this BAR_THIS
--bar_that BAR_THAT
--foo_this FOO_THIS
--foo_that FOO_THAT
在某些情况下,你需要将参数分成更多的概念部分来帮助用户。例如,你可能希望将所有输入选项放在一组,而将所有输出格式化选项放在另一组。上面的示例可以调整为将 --foo_* 参数与 --bar_* 参数分开。
import argparse
parser = argparse.ArgumentParser(description='Simple example')
parser.add_argument('name', help='Who to greet', default='World')
# Create two argument groups
foo_group = parser.add_argument_group(title='Foo options')
bar_group = parser.add_argument_group(title='Bar options')
# Add arguments to those groups
foo_group.add_argument('--bar_this')
foo_group.add_argument('--bar_that')
bar_group.add_argument('--foo_this')
bar_group.add_argument('--foo_that')
args = parser.parse_args()
当运行 python example.py -h 时,会产生这样的输出:
usage: example.py [-h] [--bar_this BAR_THIS] [--bar_that BAR_THAT]
[--foo_this FOO_THIS] [--foo_that FOO_THAT]
name
Simple example
positional arguments:
name Who to greet
optional arguments:
-h, --help show this help message and exit
Foo options:
--bar_this BAR_THIS
--bar_that BAR_THAT
Bar options:
--foo_this FOO_THIS
--foo_that FOO_THAT

7: 使用 docopt 和 docopt_dispatch 的高级示例
和 docopt 一样,使用 [docopt_dispatch] 时,你要在入口点模块的 __doc__ 变量中制作你的 --help。在那里,你以 doc string 作为参数调用 dispatch,这样它就可以运行解析器了。
这样,你就不用手动处理参数(通常会导致高循环的 if/else 结构),而只需让调度给出你想如何处理参数集。
这就是 dispatch.on 装饰器的作用:您只需向它提供应触发函数的参数或参数序列,该函数就会以匹配值作为参数执行。
"""Run something in development or production mode.
Usage: run.py --development
run.py --production
run.py items add -
run.py items delete
-
"""
from docopt_dispatch import dispatch
@dispatch.on('--development')
def development(host, port, **kwargs):
print('in *development* mode')
@dispatch.on('--production')
def development(host, port, **kwargs):
print('in *production* mode')
@dispatch.on('items', 'add')
def items_add(item, **kwargs):
print('adding item...')
@dispatch.on('items', 'delete')
def items_delete(item, **kwargs):
print('deleting item...')
if __name__ == '__main__':
dispatch(__doc__)