返回介绍

建议41:使用 argparse 处理命令行参数

发布于 2024-01-30 22:19:09 字数 5293 浏览 0 评论 0 收藏 0

尽管应用程序通常能够通过配置文件在不修改代码的情况下改变行为,但提供灵活易用的命令行参数依然非常有意义,比如:减轻用户的学习成本,通常命令行参数的用法只需要在应用程序名后面加--help参数就能获得,而配置文件的配置方法通常需要通读手册才能掌握;同一个运行环境中有多个配置文件存在,那么需要通过命令行参数指定当前使用哪一个配置文件,如pylint的--rcfile参数就是做这个事的。

为了做好命令行处理这件事,Pythonista尝试好几个方案,标准库中留下的getopt、optparse和argparse就是证明。其中getopt是类似UNIX系统中getopt()这个C函数的实现,可以处理长短配置项和参数。如有命令行参数-a -b -cfoo -d bar a1 a2,在处理之后的结果是两个列表,其中一个是配置项列表[('-a', ''), ('-b', ''), ('-c', 'foo'), ('-d', 'bar')],每一个元素都由配置项名和其值(默认为空字符串)组成;另一个是参数列表['a1', 'a2'],每一个元素都是一个参数值。getopt的问题在于两点,一个是长短配置项需要分开处理,二是对非法参数和必填参数的处理需要手动。如:

  try:
    opts, args = getopt.getopt(sys.argv[1:], "ho:v", ["help", "output="])
  except getopt.GetoptError as err:
    print str(err) # 
此处输出类似 "option -a not recognized" 
的出错信息
    usage()
    sys.exit(2)
  output = None
  verbose = False
  for o, a in opts:
    if o == "-v":
      verbose = True
    elif o in ("-h", "--help"):
      usage()
      sys.exit()
    elif o in ("-o", "--output"):
      output = a
    else:
      assert False, "unhandled option"

从for循环处可以看到,这种处理非常原始和不便,而从getopt.getopt(sys.argv[1:], "ho:v", ["help", "output="])函数调用时的"ho:v" 和 ["help", "output="]两个实参可以看出,要编写和维护还是比较困难的,所以optparse就登场了。optparse比getopt要更加方便、强劲,与C风格的getopt不同,它采用的是声明式风格,此外,它还能够自动生成应用程序的帮助信息。下面是一个例子:

from optparse import OptionParser
parser = OptionParser()
parser.add_option("-f", "--file", dest="filename",
          help="write report to FILE", metavar="FILE")
parser.add_option("-q", "--quiet",
          action="store_false", dest="verbose", default=True,
          help="don't print status messages to stdout")
 (options, args) = parser.parse_args()

可以看到add_option()方法非常强大,同时支持长短配置项,还有默认值、帮助信息等,简单的几行代码,可以支持非常丰富的命令行接口。如,以下几个都是合法的应用程序调用:

<yourscript> -f outfile --quiet
<yourscript> --quiet --file outfile
<yourscript> -q -foutfile
<yourscript> -qfoutfile

除此之外,虽然没有声明帮助参数,但默认给加上了-h或--help支持,通过这两个参数调用应用程序,可以看到自动生成的帮助信息。

Usage: <yourscript> [options]
Options:
  -h, --help      show this help message and exit
  -f FILE, --file=FILE  write report to FILE
  -q, --quiet       don't print status messages to stdout

不过 optparse虽然很好,但是后来出现的argparse在继承了它声明式风格的优点之外,又多了更丰富的功能,所以现阶段最好用的参数处理标准库是argparse,使optparse成为了一个被弃用的库。

因为argparse自optparse脱胎而来,所以用法倒也大致相同,都是先生成一个parser实例,然后增加参数声明。如上文中getopt的那个例子,可以用其改造为如下形式:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-o', '--output')
parser.add_argument('-v', dest='verbose', action='store_true')
args = parser.parse_args()

可以看到,代码大大地减化了,代码更少,bug更少。与optparse中的add_option()类似,add_argument()方法用以增加一个参数声明。与add_option()相比,它有几个方面的改进,其中之一就是支持类型增多,而且语法更加直观。表现在type参数的值不再是一个字符串,而是一个可调用对象,比如在add_option()调用时是type="int",而在add_argument()调用时直接写type=int就可以了。除了支持常规的int/float等基本数值类型外,argparse还支持文件类型,只要参数合法,程序就能够使用相应的文件描述符。如:

>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('bar', type=argparse.FileType('w'))
>>> parser.parse_args(['out.txt'])
Namespace(bar=<open file 'out.txt', mode 'w' at 0x...>)

另外,扩展类型也变得更加容易,任何可调用对象,比如函数,都可以作为type的实参。与type类似,choices参数也支持更多的类型,而不是像add_option那样只有字符串。比如下面这句代码是合法的:

parser.add_argument('door', type=int, choices=range(1, 4))

此外,add_argument()提供了对必填参数的支持,只要把required参数设置为True传递进去,当缺失这一参数时,argparse就会自动退出程序,并提示用户。

如果仅仅是add_argument()比add_option()更加强大一点,并不足以让它把optparse踢出标准库,ArgumentParser还支持参数分组。add_argument_group()可以在输出帮助信息时更加清晰,这在用法复杂的CLI应用程序中非常有帮助,比如setuptools配套的setup.py文件,如果运行python setup.py help可以看到它的参数是分组的。下面是一个简单的示例:

>>> parser = argparse.ArgumentParser(prog='PROG', add_help=False)
>>> group1 = parser.add_argument_group('group1', 'group1 description')
>>> group1.add_argument('foo', help='foo help')
>>> group2 = parser.add_argument_group('group2', 'group2 description')
>>> group2.add_argument('--bar', help='bar help')
>>> parser.print_help()
usage: PROG [--bar BAR] foo
group1:
  group1 description
  foo  foo help
group2:
  group2 description
  --bar BAR  bar help

如果仅仅是更加漂亮的帮助信息输出不够吸引你,那么add_mutually_exclusive_group(required=False)就非常实用:它确保组中的参数至少有一个或者只有一个(required=True)。

argparse也支持子命令,比如pip就有install/uninstall/freeze/list/show等子命令,这些子命令又接受不同的参数,使用ArgumentParser.add_subparsers()就可以实现类似的功能。

>>> import argparse
>>> parser = argparse.ArgumentParser(prog='PROG')
>>> subparsers = parser.add_subparsers(help='sub-command help')
>>> parser_a = subparsers.add_parser('a', help='a help')
>>> parser_a.add_argument('--bar', type=int, help='bar help')
>>> parser.parse_args(['a', '--bar', '1'])
Namespace(bar=1)

看,就是这么简单!除了参数处理之外,当出现非法参数时,用户还需要做一些处理,处理完成后,一般是输出提示信息并退出应用程序。ArgumentParser提供了两个方法函数,分别是exit(status=0, message=None)和error(message),可以省了import sys再调用sys.exit()的步骤。

注意

虽然argparse已经非常好用,但是上进的Pythonista并没有止步,所以他们发明了docopt,可以认为,它是比argparse更先进更易用的命令行参数处理器。它甚至不需要编写代码,只要编写类似argparse输出的帮助信息即可。这是因为它根据常见的帮助信息定义了一套领域特定语言(DSL),通过这个DSL Parser参数生成处理命令行参数的代码,从而实现对命令行参数的解释。因为docopt现在还不是标准库,所以在此不多介绍,有兴趣的读者可以自行去其官网(http://docopt.org/)学习。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文