optparse模块代码分析

时间:2021-11-28 23:18:01

optparse是标准库里用于分析命令行参数的模块(路径:/usr/lib/python2.x/optparse.py, x86-64版本的系统在lib64下)。使用它,可以让自己的程序方便地获得命令行参数,无需再自己处理了。至少也是让程序更标准化。本文首先简单介绍optparse的使用,详细用法请参照官方文档介绍。接着,详细地分析optparse如何实现这些功能的,optparse的代码结构是什么样子的。

一、 optparse模块使用简介

创建client.py,实现的功能是向server发送一个字符串信息。命令行的参数形式如下:

Usage: client.py [options] message

Options:
  -h, --help            show this help message and exit
  -p PORT, --port=PORT  port number which server listens on
  -s SERVER_ADDR, --server=SERVER_ADDR
                        server address
  -q, --quiet           don't print status message to stdout


client.py的代码内容如下:

  1 # -*- coding:utf-8 -*-
  2 # 包含中文,必须有第一行的声明。
  3 from optparse import OptionParser
  4 
  5 usage = "%prog [options] message"
  6 parser = OptionParser(usage)    #创建一个OptionParser对象(包含多个Option对象的容器)
  7 parser.add_option('-p', '--port', action='store',  #在容器对象里,添加一个Option对象
  8                   type='int', default=7890, dest='port', #选项值放在‘port’里
  9                   help='port number which server listens on')
 10 parser.add_option('-s', '--server', action='store',
 11                   type='string', dest='server_addr',  #选项值放在‘server_addr’里
 12                   help='server address')
 13 parser.add_option('-q', '--quiet', action='store_false',
 14                   dest='verbose', default=True,       #选项值放在‘verbose’里
 15                   help="don't print status message to stdout")
 16 options, args = parser.parse_args()  #解析命令行参数,将选项参数值放在options里,参数放在args里
 17 print "Server address:", options.server_addr #访问server_addr选项
 18 print "Server port   :", options.port        #访问port选项
 19 print "Verbose       :", options.verbose     #访问verbose选项
 20 print "message       :", args                #访问参数选项

以上短短几行代码就把想要的功能实现了。接下来,让我们看看它是如何做到的。


二、 optparse模块的代码分析。

分析模块的代码,首先,应该要熟悉模块的使用方法,如此才能知道代码为什么要这样做,这个很重要。再者,对于不理解的地方,可以使用import pdb做调试观察。

optparse的代码只有1703行,包含的内容不多,主要的只有Option类和OptionContainer类(容器),这也是最常被使用到的。所以,下面只介绍这两个部分。

1. Option类:

上文,我们调用parser.add_option('-p', '--port', action='store', type='int', default=7890, dest='port',  help='port number which server listens on')时,实际上就是创建一个Option类,也就是给程序创建一个选项。那么,一个选项会包含哪些东西呢?如我们调用程序时常见到的,通常有以下几种形式:

“<program> -v  -d  message"  #两个选项-v 和-d分开

”<program> -vd message" #两个选项写在一起

"<program> -f outfile"  #使用短选项-f 指定输出文件,选项名和参数用空格隔开

"<program> -foutfile"  #选项名和参数连在一起

"<program> --file outfile" #使用长选项--file指定输出文件,选项名和参数用空格隔开

"<program> --file=outfile" “选项名和参数用”=“连接

从上可以看到,一个选项通常包含一个短选项名(如”-f“),一个长选项名(如”--file“),以及跟着一个参数(有些选项不需要),至少需要包含一个短选项名或者长选项名。每个选项其实就是对某一个attribue设置一个value。那么dest='port'就是指明attribute的名称是dest, default=7890指value的默认值为7890,这个value也可以从调用命令的参数行中获取。type=‘int’指value的类型是整数,optparse支持多种类型,主要是string(字符串),int(整数),float(浮点),True, False。help=“..."指输出帮助信息说明该选项的用途。所以一个Option对象,会有以下几个属性值:

  • 短选项名称列表
  • 长选项名称列表
  • dest(attribute名称)
  • default(指定默认值)
  • type(value的类型)
  • help (帮助信息)
  • action (通常是store,还有store_const, store_true, store_false。)

下面是Option类的初始化方法:

560     def __init__(self, *opts, **attrs):

                 #opts接收选项名称参数列表,如['-f', '--file']

                 #attrs接收dest,default,type这些属性设置参数
 563         self._short_opts = []   #短选项名称列表
 564         self._long_opts = []    #长选项名称列表
 565         opts = self._check_opt_strings(opts)   #检查选项名称列表opts,至少应该有一个短选项名,或一个长选项名
 566         self._set_opt_strings(opts) #解析opts列表的每个成员是否合法,以'-'或'--'开头,

                                                                   #否则抛出OptionError异常。最后,将合法的成员分别

                                                                   #放入对应列表self._shor_opts或者self._long_opts。
 567 
 568         # Set all other attrs (action, type, etc.) from 'attrs' dict
 569         self._set_attrs(attrs)             #解析attrs字典(包含属性设置参数)
 576         for checker in self.CHECK_METHODS:  #检查每个属性设置是否合法
 577             checker(self)

self._set_attrs(attrs)的代码如下,Option类包含一个属性值列表 ATTRS = ['action',  'type',  'dest', 'default',  'nargs', 'const', 'choices',
  'callback',  'callback_args', 'callback_kwargs',  'help', 'metavar']。

 609     def _set_attrs(self, attrs):
 610         for attr in self.ATTRS:   #遍历这个列表的每个attr
 611             if attr in attrs:  #如果attr也存在于创建Option对象时声明的attrs字典中
 612                 setattr(self, attr, attrs[attr])  #从字典中取出,并设置为对象的一个属性。

                                                                        #例如dest='port',则会得到self.dest='port'
 613                 del attrs[attr]                         #从attrs字典中弹出
 614             else:
 615                 if attr == 'default':
 616                     setattr(self, attr, NO_DEFAULT)
 617                 else:
 618                     setattr(self, attr, None) #设置这个属性的值为None
 619         if attrs: #如果attrs字典不为空,表明含有非法的属性名称,抛出异常。
 620             attrs = attrs.keys()
 621             attrs.sort()
 622             raise OptionError(
 623                 "invalid keyword arguments: %s" % ", ".join(attrs),
 624                 self)

CHECK_METHODS是一个回调函数列表,每个回调函数以Option对象为参数,执行一项检查。每个检查是独立的,可以很方便地增加或者修改一个检查。

 736     CHECK_METHODS = [_check_action,
 737                                               _check_type,
 738                                              _check_choice,
 739                                               _check_dest,
 740                                             _check_const,
 741                                             _check_nargs,
 742                                            _check_callback]


629     def _check_action(self):
 630         if self.action is None:
 631             self.action = "store"
 632         elif self.action not in self.ACTIONS:
 633             raise OptionError("invalid action: %r" % self.action, self)

Option类的其他方法,都是些辅助的方法。比较重要的两个就是process和take_action,这是在解释命令行参数时会被OptionParser(容器对象)调用到的,目的的是检查对应的参数选项的值是否合法,以及对这个参数值应该怎么处理。例如,当一个选项声明自己是‘int’类型的参数,那么它的值就不能是‘string’。如果命令行输入‘-h'选项参数,则take_action会输出帮助信息,并退出。一般的情况下,则是存储参数值,然后执行下一步。

2. OptionParser类

OptionParesr继承于OptionContainer类。整个模块里,也就只有这两个容器类,所以可以简单地认为就是这两个类功能的合集。我们只主要查看我们使用到的两个方法add_option() 和parse_args()。

首先,OptionParser包含的几个主要的属性值:

_short_opt: {string: Optiong}字典对象。一个短选项名(如, "-f")对应一个Option对象。

_long_opt: {string: Option }。一个长选项名(如:"--file")对应一个Option对象。

defaults: {string: andy } 。 键为dest , 值为缺省值。

option_list: 列表。容器所包含的Option对象。

下面是add_option的代码内容:

1007     def add_option(self, *args, **kwargs):
1011         if type(args[0]) in types.StringTypes:
1012             option = self.option_class(*args, **kwargs)    
#创建一个Option类
1013         elif len(args) == 1 and not kwargs:    #传入的参数是一个Option对象的情况,较少用
1014             option = args[0]
1015             if not isinstance(option, Option):
1016                 raise TypeError, "not an Option instance: %r" % option
1017         else:
1018             raise TypeError, "invalid arguments"
1019 
1020         self._check_conflict(option)  #检查新增的参数选项是否和之前的有冲突。

                                                                      #方法:创建一个空的冲突选项列表。遍历option对象的所有短选项名,

                                                                      #如果存在于容器对象的_shor_opt字典里,则添加到冲突选项列表。

                                                                      #同样的方法,处理长选项名。

                                                                      #最后如果冲突选项列表不空,则表示有冲突,调用冲突处理方法conflict_handler。
1021 
1022         self.option_list.append(option)  #添加到列表中
1023         option.container = self
1024         for opt in option._short_opts:   #对短选项名处理,添加到_short_opt字典中
1025             self._short_opt[opt] = option
1026         for opt in option._long_opts:    #对长选项名处理。
1027             self._long_opt[opt] = option
1028 
1029         if option.dest is not None:     #选项对应的缺省值处理
1030             if option.default is not NO_DEFAULT:
1031                 self.defaults[option.dest] = option.default
1032             elif option.dest not in self.defaults:
1033                 self.defaults[option.dest] = None
1034 
1035         return option

下面是parse_args()的代码:

1367     def parse_args(self, args=None, values=None):
1381         rargs = self._get_args(args)  
#通常args为None,返回命令行参数列表。(不包含程序名)

                                                                       #调用sys.argv[1:]
1382         if values is None:   #values是一个Value对象,可以认为是一个属性值容器。

                                                    #每个选项最终都会对应一个键值对。例如, (’-p‘, dest='port', default=7890)。

                                                    #最终变成values.port=7890。这是缺省值。如果命令行参数输入 “-p 8890”,

                                                    #则变成values.port=8890。
1383             values = self.get_default_values()
1394         self.rargs = rargs  

1395         self.largs = largs = []
1396         self.values = values
1397 
1398         try:
1399             stop = self._process_args(largs, rargs, values)#解析命令行参数,主要是处理选项参数,

                                                                                                             #如果有选项设置改变了缺省值,最终改变values这个属性值容器的某个属性。

                                                                                                             #末尾的参数放在rargs里
1400         except (BadOptionError, OptionValueError), err:
1401             self.error(str(err))
1402 
1403         args = largs + rargs
1404         return self.check_values(values, args)  #这个方法未作任何处理,最终返回(values, args)

处理的流程很简单。接下来看_process_args()的代码,如下。主要看其如何处理长选项名参数,和短选项名参数。

1419     def _process_args(self, largs, rargs, values):
1429         while rargs:
#rargs是命令行参数列表,由命令行参数字符串用空格切割得到。

                                        #遍历每个参数,分别对其进行解析。
1430             arg = rargs[0]
1434             if arg == "--": 
1435                 del rargs[0]
1436                 return
1437             elif arg[0:2] == "--": #以‘--’开头,说明这是一个长选项名参数
1438                 # process a single long option (possibly with value(s))
1439                 self._process_long_opt(rargs, values)
1440             elif arg[:1] == "-" and len(arg) > 1:#以‘-’开头,说明这是一个短选项名参数
1441                 # process a cluster of short options (possibly with
1442                 # value(s) for the last one only)
1443                 self._process_short_opts(rargs, values)
1444             elif self.allow_interspersed_args:
1445                 largs.append(arg)
1446                 del rargs[0]
1447             else:
1448                 return                  # stop now, leave this arg in rargs

_process_long_opt()是处理长选项名的方法,代码如下:

1479     def _process_long_opt(self, rargs, values):
1480         arg = rargs.pop(0)
#从命令行参数列表rargs弹出
1484         if "=" in arg:   #对于“--file=outfile”的形式
1485             (opt, next_arg) = arg.split("=", 1) 
1486             rargs.insert(0, next_arg) #将‘outfile’又放入命令行参数中,后面的处理会用到这个。

                                                                   #等同于,变换成“--file outfile”
1487             had_explicit_value = True #表明这个参数选项,确定跟着一个参数值。
1488         else:  #对于“--file outfile”的形式
1489             opt = arg
1490             had_explicit_value = False
1491 
1492         opt = self._match_long_opt(opt) #检查这个选项名,是不是我们所定义过的
1493         option = self._long_opt[opt] #取得它所对应的Option对象
1494         if option.takes_value(): #这个选项名后应该跟着一个或者多个参数
1495             nargs = option.nargs #获得这个选项后应该跟着的参数个数,缺省值为1个
1496             if len(rargs) < nargs: #命令行参数列表的个数已经少于需要的个数,抛出异常
1497                 if nargs == 1:
1498                     self.error(_("%s option requires an argument") % opt)
1499                 else:
1500                     self.error(_("%s option requires %d arguments")
1501                                % (opt, nargs))
1502             elif nargs == 1:  #只需要一个参数,直接取出后面的一个参数放在value中
1503                 value = rargs.pop(0)
1504             else: #多于一个参数,从后面取出指定数量的参数放在列表value中
1505                 value = tuple(rargs[0:nargs])
1506                 del rargs[0:nargs]  
1507 
1508         elif had_explicit_value: #选项后不需要跟着参数,但实际确有参数,抛出异常。

                                                            #例如,('--quiet', action=’store_true',),‘quiet'选项不许要参数,

                                                            #但是命令行输入’--quiet=yes', 这样是错误的。
1509             self.error(_("%s option does not take a value") % opt)
1510 
1511         else:
1512             value = None
1513 
1514         option.process(opt, value, values, self) #将这个选项对应的参数值进行处理,并放在values中。

                                                                                          #对于‘-h’选项,则是输出帮助信息,然后退出程序。

_process_short_opts对于短参数名称的处理,跟_process_long_opts()类似。

总结一下,OptionParser处理命令行参数的过程。以我们创建的client.py为例,当输入python client.py -s 10.2.20.99 --quiet “it's nice"时,处理的流程如下:

a. 从sys.argv[:1]获取命令行参数(如:[" -s" , " 10.2.20.99", " --quiet", "It's nice."])

b.取出‘-s', 这是以’-s'开头,调用_process_short_opts(),查找容器对象的_short_opt字典,找到对应的Option对象。没找到则抛出异常。

c.调用option.takes_value()。得到这个Option对象后面应该跟着一个参数,从命令行参数列表取出“10.2.20.99”,放入value中。

d.调用option.process(opt, value, values, self)。将检查value是否合法,然后存在values中,即values.server_addr = "10.2.20.99"。

e.处理下一个"--quiet", 这是以‘--’开头的,调用_process_long_opts(), 查找容器对象的_long_opt字典,找到对应的Option对象。

f. 调用option.takes_value(), 发现这个Option对象后面不需要跟着参数。value=None。

g.调用option.process(opt, value, values, self)。根据这个Option对象的action=‘store_false',即将值设置为False。所以,values.verbose=False(因为dest=’verbose‘)。

h.取出“It’s nice”,这不是以‘-’或‘--’开头的,所以不是一个选项参数。命令行参数处理结束,将“It‘s nice”放在args中返回。

i.从opt,args=parser.parse_args()等到的是一个Value对象opt, args参数列表。在这里args=“it’s nice”, opt.server_addr="10.2.20.99", opt.verbose=False。


总结:

本文只是介绍了两个主要的类:Option和OptionParser,和两个主要的方法add_option()和parse_args()。其余的部分,主要似乎Error异常类的,和Formatter(帮助信息输出格式化)。看别人代码如何实现其主要的功能,这是个简单的过程。但是要想理解作者但是在创建的过程中的想法,这是比较难的。更难的是,把这种想法变成自己的,并加以利用和改进。这是向别人学习的过程,没那么容易。

收获: 1.一直对标准库代码有点恐惧。那得是多么高深的啊,我怎么可能看懂?其实,也没那么难,至少optparse是我能消化的。当然也有很复杂的。首先消除了恐惧的心里,从简单的入手,复杂的以后再解决。2.在看代码的过程中,可以学习很多东西,代码的编写风格,算法思路,很多。这会是一个潜移默化的过程。我会坚持一段时间,每周分析一个简单的标准库模块。下一个是logging模块。

 写这个文章花了我5个小时,但我还是觉得,别人不一定能看懂。也许我应该考虑更好的表述方式,流程图,类的描述图。这也是我欠缺的能力。