关于python:解析命令行参数的最佳方法是什么?

关于python:解析命令行参数的最佳方法是什么?

What's the best way to parse command line arguments?

解析python命令行参数最简单、最简洁、最灵活的方法或库是什么?


其他答案确实提到了对于新的python来说,argparse是一种方法,但不给出使用示例。为了完整起见,下面简要总结了如何使用argparse:

1)初始化

1
2
3
4
import argparse

# Instantiate the parser
parser = argparse.ArgumentParser(description='Optional app description')

2)添加参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Required positional argument
parser.add_argument('pos_arg', type=int,
                    help='A required integer positional argument')

# Optional positional argument
parser.add_argument('opt_pos_arg', type=int, nargs='?',
                    help='An optional integer positional argument')

# Optional argument
parser.add_argument('--opt_arg', type=int,
                    help='An optional integer argument')

# Switch
parser.add_argument('--switch', action='store_true',
                    help='A boolean switch')

3)解析

1
args = parser.parse_args()

4)访问权限

1
2
3
4
5
print("Argument values:")
print(args.pos_arg)
print(args.opt_pos_arg)
print(args.opt_arg)
print(args.switch)

5)检查值

1
2
if args.pos_arg > 10:
    parser.error("pos_arg cannot be larger than 10")

用法

正确使用:

1
2
3
4
5
6
7
$ ./app 1 2 --opt_arg 3 --switch

Argument values:
1
2
3
True

参数不正确:

1
2
3
4
5
6
7
8
9
10
11
12
$ ./app foo 2 --opt_arg 3 --switch
usage: convert [-h] [--opt_arg OPT_ARG] [--switch] pos_arg [opt_pos_arg]
app: error: argument pos_arg: invalid int value: 'foo'

$ ./app 11 2 --opt_arg 3
Argument values:
11
2
3
False
usage: app [-h] [--opt_arg OPT_ARG] [--switch] pos_arg [opt_pos_arg]
convert: error: pos_arg cannot be larger than 10

完全帮助:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ ./app -h

usage: app [-h] [--opt_arg OPT_ARG] [--switch] pos_arg [opt_pos_arg]

Optional app description

positional arguments:
  pos_arg            A required integer positional argument
  opt_pos_arg        An optional integer positional argument

optional arguments:
  -h, --help         show this help message and exit
  --opt_arg OPT_ARG  An optional integer argument
  --switch           A boolean switch

这个答案建议使用optparse,它适用于旧的Python版本。对于python 2.7及更高版本,argparse取代optparse。有关详细信息,请参阅此答案。

正如其他人指出的那样,你最好是和optparse一起而不是getopt。getopt几乎是标准getopt(3)c库函数的一对一映射,而且不容易使用。

optparse虽然有点冗长,但它的结构要好得多,在以后的扩展中也更简单。

下面是一个典型的向解析器添加选项的行:

1
2
3
parser.add_option('-q', '--query',
            action="store", dest="query",
            help="query string", default="spam")

它基本上说明了它自己;在处理时,它将接受-q或--query作为选项,将参数存储在一个名为query的属性中,如果不指定该属性,则具有默认值。它也是自文档化的,因为您声明了帮助参数(与-h/--help一起运行时将使用该参数)和选项。

通常,您用以下方式分析您的参数:

1
options, args = parser.parse_args()

默认情况下,这将解析传递给脚本的标准参数(sys.argv[1:]

然后,options.query将被设置为传递给脚本的值。

只需通过执行

1
parser = optparse.OptionParser()

这些都是你需要的基础。下面是一个完整的python脚本,其中显示了:

1
2
3
4
5
6
7
8
9
10
11
import optparse

parser = optparse.OptionParser()

parser.add_option('-q', '--query',
    action="store", dest="query",
    help="query string", default="spam")

options, args = parser.parse_args()

print 'Query string:', options.query

5行python,向您展示基础知识。

将其保存在sample.py中,并使用

1
python sample.py

一次与

1
python sample.py --query myquery

除此之外,您将发现optparse非常容易扩展。在我的一个项目中,我创建了一个命令类,它允许您轻松地在命令树中嵌套子命令。它大量使用optparse将命令链接在一起。这不是我能用几行轻松解释的东西,但是在我的存储库中随意浏览主类,以及使用它的类和选项分析器。


使用DOCOPT

自2012年以来,python有一个非常简单、强大且非常酷的用于参数解析的模块,叫做docopt。它与Python2.6到3.5一起工作,不需要安装(只需复制它)。以下是从IT文档中获取的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
"""Naval Fate.

Usage:
  naval_fate.py ship new <name>...
  naval_fate.py ship <name> move <x> <y> [--speed=<kn>]
  naval_fate.py ship shoot <x> <y>
  naval_fate.py mine (set|remove) <x> <y> [--moored | --drifting]
  naval_fate.py (-h | --help)
  naval_fate.py --version

Options:
  -h --help     Show this screen.
  --version     Show version.
  --speed=<kn>  Speed in knots [default: 10].
  --moored      Moored (anchored) mine.
  --drifting    Drifting mine.

"""

from docopt import docopt


if __name__ == '__main__':
    arguments = docopt(__doc__, version='Naval Fate 2.0')
    print(arguments)

这就是它:2行代码加上您的文档字符串,这是必需的,您可以在arguments对象中分析和使用参数。我告诉过你这很酷,不是吗;-)

使用python fire

自2017年以来,python fire有了另一个很酷的模块,它可以在您进行零参数解析时为代码提供一个cli接口。以下是文档中的一个简单示例(这个小程序向命令行公开函数double):

1
2
3
4
5
6
7
8
9
import fire

class Calculator(object):

  def double(self, number):
    return 2 * number

if __name__ == '__main__':
  fire.Fire(Calculator)

在命令行中,可以运行:

1
2
3
4
> calculator.py double 10
20
> calculator.py double --number=15
30

太棒了,不是吗?


由于这些原因,新的hip方式是argparse。argparse>optparse>getopt

更新:自PY2.7起,argparse是标准库的一部分,optparse已弃用。


我更喜欢点击。它抽象了管理选项,并允许"(…)以一种可组合的方式创建漂亮的命令行接口,只需要很少的代码"。

以下是示例用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
import click

@click.command()
@click.option('--count', default=1, help='Number of greetings.')
@click.option('--name', prompt='Your name',
              help='The person to greet.')
def hello(count, name):
   """Simple program that greets NAME for a total of COUNT times."""
    for x in range(count):
        click.echo('Hello %s!' % name)

if __name__ == '__main__':
    hello()

它还自动生成格式良好的帮助页面:

1
2
3
4
5
6
7
8
9
$ python hello.py --help
Usage: hello.py [OPTIONS]

  Simple program that greets NAME for a total of COUNT times.

Options:
  --count INTEGER  Number of greetings.
  --name TEXT      The person to greet.
  --help           Show this message and exit.

几乎每个人都在使用getopt

以下是文档的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import getopt, sys

def main():
    try:
        opts, args = getopt.getopt(sys.argv[1:],"ho:v", ["help","output="])
    except getopt.GetoptError:
        # print help information and exit:
        usage()
        sys.exit(2)
    output = None
    verbose = False
    for o, a in opts:
        if o =="-v":
            verbose = True
        if o in ("-h","--help"):
            usage()
            sys.exit()
        if o in ("-o","--output"):
            output = a

总之,这就是它的工作原理。

你有两种选择。接受争论的人就像开关一样。

sys.argv基本上就是c中的char** argv。就像c中一样,跳过程序名的第一个元素,只分析参数:sys.argv[1:]

Getopt.getopt将根据您在参数中给出的规则来解析它。

这里描述的是简短的论点:-ONELETTER:表示-o接受一个论点。

最后,["help","output="]描述了长参数(--MORETHANONELETTER)。输出后的=再次表示输出接受一个参数。

结果是一个夫妇列表(选项、参数)

如果一个选项不接受任何参数(比如这里的--help),arg部分是一个空字符串。然后,您通常希望在此列表上循环,并像示例中那样测试选项名。

我希望这对你有帮助。


使用标准库附带的optparse。例如:

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/env python
import optparse

def main():
  p = optparse.OptionParser()
  p.add_option('--person', '-p', default="world")
  options, arguments = p.parse_args()
  print 'Hello %s' % options.person

if __name__ == '__main__':
  main()

来源:使用python创建unix命令行工具

但是,从python 2.7开始,optparse就被弃用了,请参见:为什么使用argparse而不是optparse?


轻型命令行参数默认值

虽然argparse非常好,并且是完全文档化的命令行开关和高级功能的正确答案,但是您可以使用函数参数默认值来非常简单地处理直接的位置参数。

1
2
3
4
5
6
7
import sys

def get_args(name='default', first='a', second=2):
    return first, int(second)

first, second = get_args(*sys.argv)
print first, second

'name'参数捕获脚本名,未使用。测试输出如下:

1
2
3
4
5
6
> ./test.py
a 2
> ./test.py A
A 2
> ./test.py A 20
A 20

对于我只需要一些默认值的简单脚本,我发现这已经足够了。您可能还希望在返回值中包含一些类型强制,或者命令行值都是字符串。


为了以防万一,如果需要在win32(2K、XP等)上获取unicode参数,这可能会有所帮助:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from ctypes import *

def wmain(argc, argv):
    print argc
    for i in argv:
        print i
    return 0

def startup():
    size = c_int()
    ptr = windll.shell32.CommandLineToArgvW(windll.kernel32.GetCommandLineW(), byref(size))
    ref = c_wchar_p * size.value
    raw = ref.from_address(ptr)
    args = [arg for arg in raw]
    windll.kernel32.LocalFree(ptr)
    exit(wmain(len(args), args))
startup()

我认为大型项目的最佳方法是optparse,但是如果您正在寻找一种简单的方法,也许http://werkzeug.pocoo.org/documentation/script适合您。

1
2
3
4
5
6
7
8
9
10
11
12
13
from werkzeug import script

# actions go here
def action_foo(name=""):
   """action foo does foo"""
    pass

def action_bar(id=0, title="default title"):
   """action bar does bar"""
    pass

if __name__ == '__main__':
    script.run()

所以基本上,每个函数操作都暴露在命令行中,帮助消息是免费生成的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
python foo.py
usage: foo.py  [<options>]
       foo.py --help

actions:
  bar:
    action bar does bar

    --id                          integer   0
    --title                       string    default title

  foo:
    action foo does foo

    --name                        string

我更喜欢optparse而不是getopt。这是非常声明性的:你告诉它选项的名称和它们应该具有的效果(例如,设置一个布尔字段),然后它会返回一个根据你的规范填充的字典。

http://docs.python.org/lib/module-optparse.html


这里应该提到consoleargs。它很容易使用。过来看:

1
2
3
4
5
6
7
8
9
10
11
12
from consoleargs import command

@command
def main(url, name=None):
 """
  :param url: Remote URL
  :param name: File name
 """

  print"""Downloading url '%r' into file '%r'""" % (url, name)

if __name__ == '__main__':
  main()

现在在控制台:

1
2
3
4
5
6
7
8
9
10
11
12
13
% python demo.py --help
Usage: demo.py URL [OPTIONS]

URL:    Remote URL

Options:
    --name -n   File name

% python demo.py http://www.google.com/
Downloading url ''http://www.google.com/'' into file 'None'

% python demo.py http://www.google.com/ --name=index.html
Downloading url ''http://www.google.com/'' into file ''index.html''

argparse代码不能长于实际的实现代码!

我发现,对于大多数流行的参数解析选项来说,这是一个问题,即如果您的参数只是适度的,那么用于记录它们的代码将变得非常大,无法满足它们所提供的好处。

一个相对新来的论点解析场景(我认为)是普莱西。

它使用argparse进行了一些公认的权衡,但使用了内联文档并简单地围绕main()类型的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
def main(excel_file_path:"Path to input training file.",
     excel_sheet_name:"Name of the excel sheet containing training data including columns 'Label' and 'Description'.",
     existing_model_path:"Path to an existing model to refine."=None,
     batch_size_start:"The smallest size of any minibatch."=10.,
     batch_size_stop: "The largest size of any minibatch."=250.,
     batch_size_step: "The step for increase in minibatch size."=1.002,
     batch_test_steps:"Flag.  If True, show minibatch steps."=False):
"Train a Spacy (http://spacy.io/) text classification model with gold document and label data until the model nears convergence (LOSS < 0.5)."

    pass # Implementation code goes here!

if __name__ == '__main__':
    import plac; plac.call(main)

我扩展了erco的方法,允许使用必需的位置参数和可选参数。这些应该在-d、-v等参数之前。

位置参数和可选参数可以分别用posarg(i)和optarg(i,默认)检索。当找到可选参数时,搜索选项(例如-i)的起始位置将向前移动1,以避免导致"意外"致命错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import os,sys


def HelpAndExit():
    print("<<your help output goes here>>")
    sys.exit(1)

def Fatal(msg):
    sys.stderr.write("%s: %s
"
% (os.path.basename(sys.argv[0]), msg))
    sys.exit(1)

def NextArg(i):
    '''Return the next command line argument (if there is one)'''
    if ((i+1) >= len(sys.argv)):
        Fatal("'%s' expected an argument" % sys.argv[i])
    return(1, sys.argv[i+1])

def PosArg(i):
    '''Return positional argument'''
    if i >= len(sys.argv):
        Fatal("'%s' expected an argument" % sys.argv[i])
    return sys.argv[i]

def OptArg(i, default):
    '''Return optional argument (if there is one)'''
    if i >= len(sys.argv):
        Fatal("'%s' expected an argument" % sys.argv[i])
    if sys.argv[i][:1] != '-':
        return True, sys.argv[i]
    else:
        return False, default


### MAIN
if __name__=='__main__':

    verbose = 0
    debug   = 0
    infile  ="infile"
    outfile ="outfile"
    options_start = 3

    # --- Parse two positional parameters ---
    n1 = int(PosArg(1))
    n2 = int(PosArg(2))

    # --- Parse an optional parameters ---
    present, a3 = OptArg(3,50)
    n3 = int(a3)
    options_start += int(present)

    # --- Parse rest of command line ---
    skip = 0
    for i in range(options_start, len(sys.argv)):
        if not skip:
            if   sys.argv[i][:2] =="-d": debug ^= 1
            elif sys.argv[i][:2] =="-v": verbose ^= 1
            elif sys.argv[i][:2] =="-i": (skip,infile)  = NextArg(i)
            elif sys.argv[i][:2] =="-o": (skip,outfile) = NextArg(i)
            elif sys.argv[i][:2] =="-h": HelpAndExit()
            elif sys.argv[i][:1] =="-":  Fatal("'%s' unknown argument" % sys.argv[i])
            else:                         Fatal("'%s' unexpected" % sys.argv[i])
        else: skip = 0

    print("Number 1 = %d" % n1)
    print("Number 2 = %d" % n2)
    print("Number 3 = %d" % n3)
    print("Debug    = %d" % debug)
    print("verbose  = %d" % verbose)
    print("infile   = %s" % infile)
    print("outfile  = %s" % outfile)

这是一个方法,而不是一个图书馆,它似乎对我有用。

这里的目标是简明扼要,每一个参数都由一行来解析,args为可读性而排列,代码很简单,不依赖任何特殊模块(仅OS+sys),优雅地警告丢失或未知参数,使用简单的for/range()循环,并在python 2.x和3.x上工作。

显示了两个切换标志(-d,-v)和两个由参数控制的值(-i xxx和-o xxx)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import os,sys

def HelpAndExit():
    print("<<your help output goes here>>")
    sys.exit(1)

def Fatal(msg):
    sys.stderr.write("%s: %s
"
% (os.path.basename(sys.argv[0]), msg))
    sys.exit(1)

def NextArg(i):
    '''Return the next command line argument (if there is one)'''
    if ((i+1) >= len(sys.argv)):
        Fatal("'%s' expected an argument" % sys.argv[i])
    return(1, sys.argv[i+1])

### MAIN
if __name__=='__main__':

    verbose = 0
    debug   = 0
    infile  ="infile"
    outfile ="outfile"

    # Parse command line
    skip = 0
    for i in range(1, len(sys.argv)):
        if not skip:
            if   sys.argv[i][:2] =="-d": debug ^= 1
            elif sys.argv[i][:2] =="-v": verbose ^= 1
            elif sys.argv[i][:2] =="-i": (skip,infile)  = NextArg(i)
            elif sys.argv[i][:2] =="-o": (skip,outfile) = NextArg(i)
            elif sys.argv[i][:2] =="-h": HelpAndExit()
            elif sys.argv[i][:1] =="-":  Fatal("'%s' unknown argument" % sys.argv[i])
            else:                         Fatal("'%s' unexpected" % sys.argv[i])
        else: skip = 0

    print("%d,%d,%s,%s" % (debug,verbose,infile,outfile))

nextarg()的目标是在检查丢失的数据时返回下一个参数,当使用nextarg()时,"skip"跳过循环,将标志解析保持在一行中。


推荐阅读