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

用于解析Python命令行参数的简单的简洁和大多数灵活的方法或库是什么?

319365 次浏览

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

#!/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?

几乎每个人都在使用getopt

下面是文档的示例代码:

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将根据你在参数中给出的规则解析它。

这里的"ho:v"描述了简短的参数::表示-o接受一个参数。

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

结果是一对(选项,参数)的列表

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

我希望这对你有所帮助。

以防万一,如果你在Win32 (2K, XP等)上需要抓住 unicode参数,这可能会有帮助:


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而不是getopt。它非常具有声明性:您告诉它选项的名称和它们应该具有的效果(例如,设置一个布尔字段),它会返回给您一个根据您的规范填充的字典。

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

这个答案建议optparse,这适用于较旧的Python版本。对于Python 2.7及以上版本,argparse取代optparse。参见this answer了解更多信息。

正如其他人指出的那样,您最好使用optparse而不是getopt。getopt基本上是标准getopt(3) C库函数的一对一映射,不太容易使用。

Optparse虽然有点冗长,但结构更好,以后更容易扩展。

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

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

它本身就说明了一切;在处理时,它将接受-q或——query作为选项,将参数存储在一个名为query的属性中,如果您没有指定它,则有一个默认值。它也是自文档化的,因为你在选项中声明了help参数(在使用-h/——help时使用)。

通常你会这样分析你的论点:

options, args = parser.parse_args()

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

选项。Query将被设置为您传递给脚本的值。

只需执行以下操作即可创建解析器

parser = optparse.OptionParser()

这些都是你需要的基本知识。下面是一个完整的Python脚本:

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中,并使用

python sample.py

有一次

python sample.py --query myquery
除此之外,您会发现optparse非常容易扩展。 在我的一个项目中,我创建了一个命令类,它允许您轻松地在命令树中嵌套子命令。它大量使用optparse将命令链接在一起。这不是我可以用几行话简单解释的事情,但是可以随意为主类在我的存储库中浏览,以及一个使用它和选项解析器的类

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

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()
所以基本上每个函数action_*都暴露在命令行和一个nice 免费生成帮助信息。< / p >
python foo.py
usage: foo.py <action> [<options>]
foo.py --help


actions:
bar:
action bar does bar


--id                          integer   0
--title                       string    default title


foo:
action foo does foo


--name                        string

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

更新:从py2.7开始,argparse是标准库的一部分,而optparse已弃用。

consoleargs值得在这里提到。它很容易使用。看看吧:

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

现在在控制台:

% 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''

使用docopt

自2012年以来,有一个非常简单、强大且真正的很酷的模块用于参数解析,称为docopt。以下是它文档中的一个例子:

"""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行代码加上你的doc字符串是必要的,你的参数被解析并在arguments对象中可用。

使用python-fire

自2017年以来,有另一个很酷的模块叫做python-fire。它可以通过解析参数为你的代码生成一个CLI界面。下面是文档中的一个简单示例(这个小程序将函数double公开给命令行):

import fire


class Calculator(object):


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


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

从命令行,你可以运行:

> calculator.py double 10
20
> calculator.py double --number=15
30

我更喜欢点击。它抽象了管理选项,并允许“(…)以一种可组合的方式,用尽可能少的代码创建漂亮的命令行界面”。

下面是用法示例:

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

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

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

argparse是要走的路。下面是如何使用它的简短总结:

1)初始化

import argparse


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

2)添加论据

# 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)解析

args = parser.parse_args()

4)访问

print("Argument values:")
print(args.pos_arg)
print(args.opt_pos_arg)
print(args.opt_arg)
print(args.switch)

5)检查价值

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

使用

正确使用方法:

$ ./app 1 2 --opt_arg 3 --switch


Argument values:
1
2
3
True

不正确的参数:

$ ./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

完整的帮助:

$ ./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

这是一个方法,而不是一个库,这似乎对我有用。

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

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

import os,sys


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


def Fatal(msg):
sys.stderr.write("%s: %s\n" % (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()的目标是在检查丢失的数据时返回下一个参数,而'skip'在使用NextArg()时跳过循环,将标志解析保持在一行内。

轻量级命令行参数默认值

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

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'参数捕获脚本名称,但不使用。测试输出如下所示:

> ./test.py
a 2
> ./test.py A
A 2
> ./test.py A 20
A 20

对于只想要一些默认值的简单脚本,我发现这已经足够了。您可能还希望在返回值中包含一些类型强制,否则命令行值将全部为字符串。

Argparse代码可能比实际实现代码还要长!

这是我在大多数流行参数解析选项中发现的一个问题,如果您的参数只是适度的,那么用于记录它们的代码就会变得与它们所提供的好处不成比例地大。

一个相对较新的参数解析场景(我认为)是温馨的

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

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, default)检索。 当找到一个可选参数时,搜索选项的起始位置(例如-i)将向前移动1,以避免导致“意外的”致命
import os,sys




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


def Fatal(msg):
sys.stderr.write("%s: %s\n" % (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)