- Preface 前言
- 第1章 引论
- 第2章 编程惯用法
- 第3章 基础语法
- 建议19:有节制地使用 from…import 语句
- 建议20:优先使用 absolute import 来导入模块
- 建议21:i+=1 不等于 ++i
- 建议22:使用 with 自动关闭资源
- 建议23:使用 else 子句简化循环(异常处理)
- 建议24:遵循异常处理的几点基本原则
- 建议25:避免 finally 中可能发生的陷阱
- 建议26:深入理解 None 正确判断对象是否为空
- 建议27:连接字符串应优先使用 join 而不是 +
- 建议28:格式化字符串时尽量使用 .format 方式而不是 %
- 建议29:区别对待可变对象和不可变对象
- 建议30:[]、() 和 {}:一致的容器初始化形式
- 建议31:记住函数传参既不是传值也不是传引用
- 建议32:警惕默认参数潜在的问题
- 建议33:慎用变长参数
- 建议34:深入理解 str() 和 repr() 的区别
- 建议35:分清 staticmethod 和 classmethod 的适用场景
- 第4章 库
- 建议36:掌握字符串的基本用法
- 建议37:按需选择 sort() 或者 sorted()
- 建议38:使用 copy 模块深拷贝对象
- 建议39:使用 Counter 进行计数统计
- 建议40:深入掌握 ConfigParser
- 建议41:使用 argparse 处理命令行参数
- 建议42:使用 pandas 处理大型 CSV 文件
- 建议43:一般情况使用 ElementTree 解析 XML
- 建议44:理解模块 pickle 优劣
- 建议45:序列化的另一个不错的选择 JSON
- 建议46:使用 traceback 获取栈信息
- 建议47:使用 logging 记录日志信息
- 建议48:使用 threading 模块编写多线程程序
- 建议49:使用 Queue 使多线程编程更安全
- 第5章 设计模式
- 第6章 内部机制
- 建议54:理解 built-in objects
- 建议55:init() 不是构造方法
- 建议56:理解名字查找机制
- 建议57:为什么需要 self 参数
- 建议58:理解 MRO 与多继承
- 建议59:理解描述符机制
- 建议60:区别 getattr() 和 getattribute() 方法
- 建议61:使用更为安全的 property
- 建议62:掌握 metaclass
- 建议63:熟悉 Python 对象协议
- 建议64:利用操作符重载实现中缀语法
- 建议65:熟悉 Python 的迭代器协议
- 建议66:熟悉 Python 的生成器
- 建议67:基于生成器的协程及 greenlet
- 建议68:理解 GIL 的局限性
- 建议69:对象的管理与垃圾回收
- 第7章 使用工具辅助项目开发
- 第8章 性能剖析与优化
建议41:使用 argparse 处理命令行参数
尽管应用程序通常能够通过配置文件在不修改代码的情况下改变行为,但提供灵活易用的命令行参数依然非常有意义,比如:减轻用户的学习成本,通常命令行参数的用法只需要在应用程序名后面加--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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论