摘要
稍微大一点的程序,都会涉及到|命令行参数|和|配置文件|处理的问题,Python程序也不例外。俺自己写了一个叫作|AoiArgumentParser|的模组,基于Python标准库的argparse模组,并添加了一个可扩展的filter插件框架。总共实现只有400行不到的代码,却可以很大简化Python程序|命令行参数|和|配置文件|的处理。本文主要介绍俺这个模组的实现原理和设计思路,以及通过使用这个模组所演化出的一套关于Python程序|命令行参数|和|配置文件|处理的解决方案。
|命令行参数|和|配置文件|的区别与联系
|命令行参数|和|配置文件|是两个有相关,又有所区别的概念。一个程序可以只使用命令行参数,也可以只使用配置文件,当然也可以同时都使用。俺分别举个例子。
只用命令行参数的情况 很多命令行程序,比如最常见的|echo|,就只使用命令行参数,没有使用配置文件。
只用配置文件的情况 比较罕见,但我们可以想象有这么一个程序,固定从某个配置文件读取配置,那么它就不须使用命令行参数。
同时都使用命令行参数和配置文件的情况 很多配置比较复杂的程序,比如MySQL,PHP,是这种情况。他们一般用配置文件设定一套配置,可以使用命令行参数指定使用哪套配置,也可以用命令行参数微调某个具体配置项。
由此三种情况可知,一般比较简单的程序,只用命令行参数即可满足需求。配置比较复杂的程序,常常使用配置文件,并且使用命令行参数来指定使用哪一个配置文件,以及进行配置项的微调。
本文着重讨论的,是第三种情况,即同时都使用命令行参数和配置文件的情况。而要同时使用命令行参数和配置文件,俺们就需要对命令行参数和配置文件进行处理。以下俺简要讨论一下常用的进行命令行参数和配置文件进行处理的标准库模组。
简单介绍|getopt|, |optparse|, |argparse|, |configparser|
在标准库中,俺有所了解的与命令行参数和配置文件处理相关的库有|getopt|, |optparse|, |argparse|, 和|configparser|。以下分别简略介绍之。
getopt
|getopt|是一个比较简单的模组,代码在200行左右。其主要提供一个|getopt|函数,实现与Unix的|getopt|函数基本相同的功能。以下给出一个使用|getopt|模组的简单代码。
import sys
from getopt import getopt
the_optional_args, the_positional_args = getopt(sys.argv[1:], 'ab:')
print('Optional Arguments: {}'.format(the_optional_args))
print('Positional Arguments: {}'.format(the_positional_args))
在这段代码中,我们将|sys.argv|中的全部程序参数传给|getopt|函数,并指定Optional参数的Parsing规则为|'ab:'|。|'ab:'|的意思是,接收一个|-a|程序参数和一个|-b|程序参数。当冒号|:|接在一个字母后时,表示使用这个程序参数时还要给一个值,例如|program -b 123|。
|getopt|函数返回的是什么呢?是一个有两个项的tuple。第一项是所有被使用的Optional程序参数,第二项是所有被使用的Positional程序参数。例如:
program.py -a -b 123 x y z
会得到
Optional Arguments: [('-a', ''), ('-b', '123')]
Positional Arguments: ['x', 'y', 'z']
optionparse
|optparse|从Python 2.3起加入标准库,又在Python 2.7和Python 3.2中被推荐由|argparse|取代。
|optparse|与|getopt|相比,引入了|action|,|type|,|default|等概念。|action|可以指定一些处理动作例如|store|,|store_true|,|store_false|。|type|可以指定类型转换,将得到的程序参数(总是str类型)转换成指定类型(例如int类型)。|default|可以指定当一个参数未被使用时,默认值是什么。这些概念的引入,使我们在程序参数处理后能从Parser的得到的程序参数,由只能是str类型,变成了可以是其它类型。
以下给出一个使用|optparse|模组的简单代码。
import sys
from optparse import OptionParser
the_parser = OptionParser()
the_parser.add_option(
'-a',
action='store_true',
default=False,
)
the_parser.add_option(
'-b',
action='store',
default=None,
)
the_optional_args, the_positional_args = the_parser.parse_args(sys.argv[1:])
print(the_optional_args)
print(the_positional_args)
在这段代码中,我们通过|OptionParser|对象的|add_option|函数指定Optional参数的Parsing规则,再将|sys.argv|中的全部程序参数传给|parse_args|函数处理。
|parse_args|函数返回的对象和|getopt|函数一样,是一个有两个项的tuple。第一项是所有被使用的Optional程序参数,第二项是所有被使用的Positional程序参数。不同的是,第一项不是一个list,而是一个dict。例如:
program.py -a -b 123 x y z
会得到
Optional Arguments: {'a': True, 'b': 123}
Positional Arguments: ['x', 'y', 'z']
argparse
|argparse|从Python 2.7和Python 3.2起加入标准库,被推荐用来取代|optparse|取代。
|argparse|与|optparse|相比,比较大的改进是Positional参数也有了名字,以及Positional参数的个数可以指定为“零至多个”这样的不定个数的形式。
以下给出一个使用|argparse|模组的简单代码。
import sys
from argparse import ArgumentParser
the_parser = ArgumentParser()
the_parser.add_argument(
'-a',
action='store_true',
default=False,
)
the_parser.add_argument(
'-b',
action='store',
type=int,
default=None,
)
the_parser.add_argument(
'c',
action='store',
nargs = '+',
)
the_args = the_parser.parse_args(sys.argv[1:])
print('Arguments: {}'.format(the_args))
在这段代码中,我们通过|ArgumentParser|对象的|add_argument|函数指定Optional参数和Positional参数的Parsing规则,再将|sys.argv|中的全部程序参数传给|parse_args|函数处理。
因为Positional参数也有了名字,|parse_args|函数返回的对象不在是一个有两个项的tuple,而是一个|argparse.Namespace|对象。我们可以通过访问|the_args.a|的方式得到经过处理后的程序参数。运行
program.py -a -b 123 x y z
会得到
Arguments: Namespace(a=True, b=123, c=['x', 'y', 'z'])
configparser
与|getopt|,|optparse|,|argparse|不同,|configparser|不处理程序参数,而只处理ini语法的配置文件。
以下给出一个使用|configparser|模组的简单代码。
"""
#// |config.ini| file has the following contents:
[ui]
username = admin
"""
import configparser
the_parser = configparser.ConfigParser()
the_parser.read('config.ini')
print(the_parser['ui']['username'])
在这段代码中,我们通过|ConfigParser|对象的|read|函数读取配置文件|config.ini|,再以类似dict的语法从Parser得到配置文件的内容。
为什么|getopt|, |optparse|, |argparse|, |configparser|还不够方便?
从上一段的介绍俺们可以看出,|getopt|,|optparse|,|argparse|的主要目的是做程序参数处理,即将|sys.argv|中的程序参数按定义的Parsing规则分类为Positional参数和Optional参数。而|configparser|的主要目的是ini配置文件的处理,即从ini语法的配置文件中读取程序设置。二者常常搭配使用。
然而俺们可能会遇到这样的情况,即得到的程序参数或配置文件设置并不是最终需要的数据。我们可能需要对从Parser得到的数据进行进一步的处理,比如根据这些数据创建类的对象。如果是这种情况,那么即使有了标准库中|getopt|,|optparse|,|argparse|和|configparser|模组的帮助,我们仍然要自己做一些工作。如果是一个配置比较复杂的程序,这个工作量也许不小,因为还涉及到错误处理的问题。
那么有没有更加简化的方法呢?答案是肯定的,也就是俺接下来要介绍|AoiArgumentParser|模组。
|AoiArgumentParser|是什么?能做什么?
|AoiArgumentParser|是俺自己写的一个模组。在设计上,它将|程序参数|和|配置文件|的概念合二为一,统称|设置|。例如,一个程序被设计为窗口的|标题|和|宽度|可以根据设置来调整,那么|标题|和|宽度|就是两个|设置|。|AoiArgumentParser|的功能,就是确保程序的其它模组,能够直接得到需要的|设置|。在|标题|和|宽度|的例子中,这两个|设置|都是比较简单的数据类型,还不能充分显示|AoiArgumentParser|的方便性。|AoiArgumentParser|最方便的地方,在于其有一个filter架构,可以不停转换|设置|的值。我们都知道,从|程序参数|和|配置文件|得到的值,一般为str类型,需要我们进行一些处理,才能得到想要的数据类型。这个filter架构的目的,就是将这一过程自动化,并且可扩展化。当我们在指定一个程序可以有哪些|设置|的时候,也同时指定一组filters,对|设置|的值进行过滤。这样,其它模组就可以直接得到想要的数据类型。同时,由于|设置|的值的生成完全交给了filters,而错误处理又由|AoiArgumentParser|代为打理,我们自己需要写的代码就大为减少了。
那么,为什么说|程序参数|和|配置文件|的概念合二为一了呢?因为在这一组产生|设置|的值的filters中,第一个filter收到的值往往是程序参数值(str类型),而这个filter可以根据这个程序参数值,加载配置文件,再由下一个filter从配置文件中读取值。最后生成的|设置|值,是这一组filters协同处理的结果,而不再是单纯的|程序参数|值或|配置文件|值了。因此,我们说,这个filter架构,将|程序参数|和|配置文件|的概念合二为一。
值得注意的是,这里的配置文件,更可能是Python模组直接作为配置文件,而不是ini语法的配置文件。为什么呢?因为Python的动态加载模组的能力,使Python模组直接作为配置文件更加方便。Python模组配置文件的配置值,可以是任何数据类型如类的对象,而ini语法的配置文件的配置值,只能是str类型。当使用Python模组直接作为配置文件时,一般通过|程序参数|指定这个Python模组的名字,第一个filter会加载这个模组并交给第二个filter。第二个filter可以根据约定读取这个模组的某个attribute来得到|设置|值,或直接搜索这个模组从中找到需要的对象(比如某个类的所有对象)。
那么,为什么说这个filter架构是可扩展的呢?因为,例如在上一段中,实现模组加载功能的filter,叫做LoadModule。实现属性读取功能的filter,叫做GetAttribute。实现对象搜索的filter,叫做CollectAttributesByIsinstance。这三个filters,是俺自己写的,能满足俺现在的应用要求。如果当不能满足应用要求时,只需写新的filter来使用即可。当这些filters做的功能具有一定普遍性的时候(比如俺写的这三个),它们能够在不同的开发者的不同应用中得到重用。所以说这个filter架构是可扩展的。
源码
源码中的注释还算详细,但光看源码,你肯定看不出个所以然来。具体怎么用,以及为什么俺觉得其方便,需要看了Demo程序再说。
__doc__ = """
Author: kuiyuyou@gmail.con
Module: AoiU.AoiArgumentParser.__init__
"""
import sys
import traceback
from collections import OrderedDict
import argparse
from argparse import SUPPRESS
import AoiU.Lang.Module as ModuleU
from AoiU.Delegator import Delegator
class AoiArgumentParser(Delegator):
def __init__(self,
config_s=None,
default_s=None,
config_arg=None,
default_arg=None,
debug_arg=None,
):
"""
#// Argumments
config_s: A list of dicts. Each dict defines one program config.
Here a "program config" is definded as an object you can get from
this parser, after processing program arguments using this parser.
For example, if the parser object is called "the_parser", and we have
defined a program config called "abc", then after processing program
arguments using "the_parser", we can get config "abc" from "the_parser"
using code |the_parser.abc|.
It is worth noting that just because |the_parser.abc| is a result of
argument processing does not mean it is of type |str|. The type of
|the_parser.abc| is determined by what "filters" specified in |config_s|
for config "abc" to produce the value of |the_parser.abc|. The value
can be of any type.
default_s: Define default values for program configs. If a program config
has got no value from program argument explicitly or implicitly, then
its value will be found from the default values. If the value can not
be found from the default values, an error will be reported.
config_arg: Besides the program configs specified by |__init__|'s argument
|config_s|, we can allow specifying program configs directly through
program argument. This is enabled by using |__init__|'s argument
|config_arg|. For example, with |config_arg='--config'|, the program
can take an optional argument in the form |--config M|. M should be
name of a python module, which should have an attribute called |CONFIG_S|.
|M.CONFIG_S| should be in the same format as |__init__|'s argument
|config_s|. A config defined in |M.CONFIG_S| takes precedence over
the same-name config defined in |__init__|'s argument |config_s|.
This argument is designed to allow the "extreme" flexibility of
redefining program configs at the time of giving program arguments
when running the program.
default_arg: Besides the default values of program configs specified by
|__init__|'s argument |default_s|, we can allow specifying default
values directly through program argument. This is enabled by using
|__init__|'s argument |default_arg|. For example, with
|default_arg='--default'|, the program can take an optional argument
in the form |--default M|. M should be name of a python module, which
should have an attribute called |DEFAULT_S|. |M.DEFAULT_S| should be
in the same format as |__init__|'s argument |default_s|. A default
value defined in |M.DEFAULT_S| takes precedence over the same-name
default value defined in |__init__|'s argument |default_s|.
This argument is designed to allow the "extreme" flexibility of
redefining default values of program configs at the time of giving
program arguments when running the program.
debug_arg: If |debug_arg| is set to, for example, '--debug', then the
program can take an optional program argument |--debug|. If this program
argument is used when running the program, then this parser will print
debug information when some error happens.
"""
#// This class is a subclass of delegator because we want to delegate
## attribute access to the object returned by |ArgumentParser.parse_args|.
## As a result, instead of writting code |the_parser.args.abc|, we can
## write |the_parser.abc|.
Delegator.__init__(self)
#//
self._debug_arg_v = debug_arg
#//
if config_s is None:
self._config_s_given_v = []
else:
self._config_s_given_v = config_s
#//
self._config_arg_v = config_arg
#//
self._default_s_given_v = default_s
#//
self._default_arg_v = default_arg
#//
self._config_s_from_module_v = self._config_s_from_module()
## If |self._config_arg_v| is None, specifying program configs through
## program argument will not be enabled, the result is an empty list.
## Otherwise the result is a list of program configs in the module whose
## name is specified through a program argument. (The program argument
## name is determined by |self._config_arg_v| which is in turn determined
## by |__init__|'s argument |config_arg|.)
#// create program argument parser
self._argparser = argparse.ArgumentParser()
#// add program arguments
self._argparser_add_arguments()
## This function will combine |self._config_s_from_module_v| and
## |self._config_s_given_v| into a single dict, with precedence rule
## described in |__init__|'s doc string applied. The combined dict is
## |self._config_s_d|.
assert self._config_s_d
#// parse program arguments
self.args = self._argparser.parse_args()
#// Below we do something more than |argparse.ArgumentParser| has done.
## For program configs that have been specified in |self._config_s_d| but
## have not been defined in |self.args|, we define them using default
## values.
#// get default values for program configs
the_default_d = self._default_d()
#// use default values for program configs which have not been defined in |self.args|
for the_config_name in self._config_s_d.keys():
if hasattr(self.args, the_config_name):
## This means the program config has been defined
continue
if the_config_name in the_default_d:
# get the default value
the_config_value = the_default_d[the_config_name]
# add the program config to |self.args|, as a result, the program
# config has been defined with default value.
setattr(self.args, the_config_name, the_config_value)
continue
#// report error if no default value can be found for the program config
sys.stderr.write('Config |{}| is not defined.'.format(the_config_name))
sys.exit(1)
#// Below we do something more than |argparse.ArgumentParser| has done.
## We implement a filter framework to allow filtering of value of program
## configs.
#//
for the_config_name in self._config_s_d.keys():
# get the dict which contains all the info about the program config
the_config = self._config_s_d[the_config_name]
# get the filters
the_filter_s = the_config.get('filter', [])
# use the filters to filter the value of the program config
for the_filter in the_filter_s:
# get the current value of the program config
the_value = getattr(self.args, the_config_name)
try:
# use the current filter to filter the value
# the filter will return a new value
the_new_value = the_filter(config=the_config, value=the_value)
except Exception as e:
sys.stderr.write("Error: Filter |{}| failed on config |{}|'s value |{}|.\nReason: {}\n".format(
the_filter,
the_config_name,
the_value,
e,
)
)
if self._debug_is_on():
raise e
sys.exit(1)
# use the new value returned by the current filter as the value
# of the program config.
setattr(self.args, the_config_name, the_new_value)
def _debug_is_on(self):
if self._debug_arg_v is None:
return False
the_dest = self._debug_arg_v.lstrip('-').replace('-', '_')
the_debug_is_on = getattr(self.args, the_dest)
return the_debug_is_on
def _default_d(self):
if self._default_s_given_v is None:
the_default_d_given = {}
else:
the_default_d_given = self._default_s_given_v
the_default_d_from_module = self._default_d_from_module()
the_default_d_given.update(the_default_d_from_module)
return the_default_d_given
def _config_name_s(self):
the_config_name_s = list(
map(
lambda s: s['config'],
self._config_s_d
)
)
return the_config_name_s
def _config_s_from_module(self):
if self._config_arg_v is None:
return []
the_arg_text_s = sys.argv[1:]
#//
for the_arg_index, the_arg_text in enumerate(the_arg_text_s):
if the_arg_text == self._config_arg_v:
if the_arg_index >= len(the_arg_text_s) - 1:
sys.stderr.write('Error: |{}| cannot be the last argument.\n'.format(the_arg_text))
sys.exit(1)
the_module_name = the_arg_text_s[the_arg_index + 1]
break
else:
return []
#//
if self._debug_arg_v is None:
the_debug_is_on = False
else:
for the_arg_text in the_arg_text_s:
if the_arg_text == self._debug_arg_v:
the_debug_is_on = True
break
else:
the_debug_is_on = False
#//
try:
the_module = ModuleU.find_module_or_error(the_module_name)
except Exception as e:
sys.stderr.write('Error: Failed loading module |{}|.\n'.format(the_module_name))
if the_debug_is_on:
traceback.print_exc()
traceback.print_stack()
sys.exit(1)
the_config_s = getattr(the_module, 'CONFIG_S', None)
if the_config_s is None:
sys.stderr.write('Error: Module |{}| has not defined |CONFIG_S|.\n'.format(the_module_name))
if the_debug_is_on:
traceback.print_stack()
sys.exit(1)
assert the_config_s is not None
return the_config_s
def _default_d_from_module(self):
if self._default_arg_v is None:
return {}
the_dest = self._default_arg_v.lstrip('-').replace('-', '_')
the_module_name = getattr(self.args, the_dest)
if the_module_name is None:
return {}
try:
the_module = ModuleU.find_module_or_error(the_module_name)
except Exception as e:
sys.stderr.write('Error: Failed loading module |{}|.\n'.format(the_module_name))
if self._debug_is_on():
traceback.print_exc()
traceback.print_stack()
sys.exit(1)
the_default_s = getattr(the_module, 'DEFAULT_S', None)
if the_default_s is None:
sys.stderr.write('Error: Module |{}| has not defined |DEFAULT_S|.\n'.format(the_module_name))
if self._debug_is_on():
traceback.print_stack()
sys.exit(1)
assert the_default_s is not None
return the_default_s
def _argparser_add_arguments(self):
if self._debug_arg_v is not None:
self._argparser.add_argument(self._debug_arg_v, action='store_true', help='whether show debug info', default=False)
if self._config_arg_v is not None:
self._argparser.add_argument(self._config_arg_v, help='config module', default=None)
if self._default_arg_v is not None:
self._argparser.add_argument(self._default_arg_v, help='default module', default=None)
the_config_d_added = OrderedDict()
for the_config in self._config_s_from_module_v + self._config_s_given_v:
## configs from module take precedence over configs given by default.
## this meams if a config name exists in both of them, the one in the
## latter will be ignored.
if isinstance(the_config, str):
the_config_name = the_config
if the_config_name in the_config_d_added:
continue
the_config_d_added[the_config_name] = dict(config=the_config_name)
continue
if 'argument' in the_config:
if 'config' in the_config: raise ValueError(the_config)
the_config_copy = the_config.copy()
the_config_copy.pop('filter', None)
the_argument = the_config_copy.pop('argument')
the_config_copy.setdefault('default', SUPPRESS)
assert the_argument is not None
if isinstance(the_argument, (list, tuple)):
assert len(the_argument) > 0
the_argument_s = the_argument
else:
the_argument_s = [the_argument]
if 'dest' in the_config_copy:
the_config_name = the_config_copy['dest']
else:
the_config_name = the_argument_s[-1].lstrip('-')
if the_config_name in the_config_d_added:
continue
the_config_d_added[the_config_name] = the_config
the_config['config'] = the_config_name
self._argparser.add_argument(*the_argument_s, **the_config_copy)
continue
if 'argument' not in the_config:
if 'config' not in the_config: raise ValueError(the_config)
the_config_name = the_config['config']
if the_config_name in the_config_d_added:
continue
the_config_d_added[the_config_name] = the_config
continue
assert False
self._config_s_d = the_config_d_added
def _delegate__Delegator(self):
return self.args
__doc__ = """
Author: kuiyuyou@gmail.con
Module: AoiU.AoiArgumentParser.FilterLib
"""
import types
import AoiU.Lang.Module as ModuleU
import AoiU.Lang.Attribute as AttributeU
import AoiU.Lang.Function as FunctionU
import AoiU.Lang.Collection.Collector as CollectorU
from AoiU.Lang.Function.UtilClass import StrMethodReturnClassname
class LoadModule(StrMethodReturnClassname):
def __call__(self, config, value):
if isinstance(value, types.ModuleType):
return value
if not isinstance(value, str):
raise ValueError(value)
the_module_name = value
try:
the_module = ModuleU.find_module_or_error(the_module_name)
except Exception as e:
raise ValueError('Failed loading module |{}|.\n'.format(the_module_name))
return the_module
class CollectAttributesByIsinstance:
def __init__(self, classtype):
assert isinstance(classtype, type)
self._classtype = classtype
def __call__(self, config, value):
if not isinstance(value, types.ModuleType):
raise ValueError(value)
the_module = value
the_result_s = []
the_module_attribute_s = AttributeU.attributes(the_module)
the_f = lambda item: CollectorU.collector_by_isinstance(
item=item,
classtype=self._classtype,
result_s=the_result_s,
if_item_is_list='expand',
if_item_is_class='instantiate',
)
FunctionU.calls(the_f, the_module_attribute_s)
return the_result_s
class GetAttribute(StrMethodReturnClassname):
def __init__(self, attribute):
assert isinstance(attribute, str)
self._attribute_name = attribute
def __call__(self, config, value):
if value is None:
raise ValueError(value)
the_object = value
if not hasattr(the_object, self._attribute_name):
raise ValueError('Object |{}| has not defined attribute |{}|.'.format(
the_object, self._attribute_name
)
)
the_attribute = getattr(the_object, self._attribute_name)
return the_attribute
演示程序
演示程序有两个。Demo1.py和Demo2.py。
Demo1.py演示基本用法,程序只收两个简单的|设置|值,|title|和|width|。|title|被规定不能用程序参数修改,只用默认值。|width|可以用程序参数修改。Deme1.py文件的注释中有命令行的各种用法。不仅可以通过程序参数"覆写"默认值配置模组中定义的值,连定义Parsing规则的模组的值也可以通过程序参数修改,即可以“通过程序参数决定如何Parse程序参数”。
Demo2.py演示用filters从模组配置文件中读取ICar和IDriver类对象的用法。
两个Demo的注释都比较详细,但由于有多个文件,不在此一一列出。以下只列出使用|AoiArgumentParser|后,我们自己需要写的代码。包括一个|AoiArgumentParser|子类定义文件,一个Parser设定文件,一个默认值设定文件。最后列出程序文件如何创建这个|AoiArgumentParser|子类的对象并读取|设置|。
这是Demo1的|AoiArgumentParser|子类定义文件。
__doc__ = """
Module: AoiArgumentParserDeme1.App.ProgramArgumentParser
"""
import AoiArgumentParserDeme1.App.Config.Parser as Parser
import AoiArgumentParserDeme1.App.Config.Default as Default
from AoiU.AoiArgumentParser import AoiArgumentParser
class ProgramArgumentParser(AoiArgumentParser):
"""
This class is simply a subclass of AoiArgumentParser. It tells the baseclass to:
Use program configs defined in |AoiArgumentParserDeme1.App.Config.Parser.CONFIG_S|
Use default values defined in |AoiArgumentParserDeme1.App.Config.Default.DEFAULT_S|
Enable program argument |--debug| for turning on debug info.
Enable program argument |--config| for specifing a module that overrides
the program configs defined in |AoiArgumentParserDeme1.App.Config.Parser.CONFIG_S|
Enable program argument |--default| for specifing a module that overrides
the default values defined in |AoiArgumentParserDeme1.App.Default.Parser.DEFAULT_S|
"""
def __init__(self):
AoiArgumentParser.__init__(self,
debug_arg='--debug',
config_s=Parser.CONFIG_S,
config_arg='--config',
default_s=Default.DEFAULT_S,
default_arg='--default',
)
这是Demo1的Parser设定文件。
__doc__ = """
Module: AoiArgumentParserDeme1.App.Config.Parser
There are two program configs defined, |title| and |width|.
#// program config |title|
Value of program config |title| can not be configured through program argument
because we didn't define program argument for it. As a result, it uses a default
value.
#// program config |width|
Value of program config |width| can be configured through program argument because
we have defined program argument (-w and --width) for it.
"""
CONFIG_S = [
dict(config='title'),
dict(argument=['-w', '--width'], type=int),
]
这是Demo1的默认值设定文件。
__doc__ = """
Module: AoiArgumentParserDeme1.App.Config.Default
"""
DEFAULT_S = dict(
title='AoiArgumentParserDeme1 (Default)',
width=1,
)
这是Demo1的程序文件。它创建一个|ProgramArgumentParser|的对象,在创建完成时|设置|值已经准备就绪了。它像访问这个Parser的attribute一样访问这些|设置|值(title和width)。
__doc__ = """
Module: AoiArgumentParserDeme1.App.Program
This demo shows simple use of AoiArgumentParser.
It is "simple" because it directly uses program arguments given at command line,
and not use "filters" to generate config values from program arguments. (The use
of filters will be covered in Demo2.)
See doc string of modules
|AoiArgumentParserDeme1.App.ProgramArgumentParser|
|AoiArgumentParserDeme1.App.Config.Parser|
|AoiArgumentParserDeme1.App.Config.Parser2|
|AoiArgumentParserDeme1.App.Config.Default|
|AoiArgumentParserDeme1.App.Config.Default2|
for more details.
#// Command Line Usage:
Program.py
Program.py -w 9
Program.py --width 9
Program.py --config AoiArgumentParserDeme1.App.Config.Parser2
## This specifies a module containing definition of program configs that takes
## precedence over those defined in |AoiArgumentParserDeme1.App.Config.Parser|'s
## |CONFIG_S| attribute.
Program.py --config AoiArgumentParserDeme1.App.Config.Parser2 -w 9
## show error because program configs defined in Parser2 supports only program
## argument |--width|, not |-w|.
Program.py --config AoiArgumentParserDeme1.App.Config.Parser2 --width 9
Program.py --default AoiArgumentParserDeme1.App.Config.Default2
Program.py --default AoiArgumentParserDeme1.App.Config.Default2 -w 9
Program.py --default AoiArgumentParserDeme1.App.Config.Default2 --width 9
"""
from AoiArgumentParserDeme1.App.ProgramArgumentParser import ProgramArgumentParser
def main():
the_parser = ProgramArgumentParser()
print('Title: {}'.format(the_parser.title))
print('Width: {}'.format(the_parser.width))
if __name__ == '__main__':
main()
这是Demo2的|AoiArgumentParser|子类定义文件。
__doc__ = """
Module: AoiArgumentParserDeme2.App.ProgramArgumentParser
"""
import AoiArgumentParserDeme2.App.Config.Parser as Parser
import AoiArgumentParserDeme2.App.Config.Default as Default
from AoiU.AoiArgumentParser import AoiArgumentParser
class ProgramArgumentParser(AoiArgumentParser):
"""
This class is simply a subclass of AoiArgumentParser. It tells the baseclass to:
Use program configs defined in |AoiArgumentParserDeme2.App.Config.Parser.CONFIG_S|
Use default values defined in |AoiArgumentParserDeme2.App.Config.Default.DEFAULT_S|
Enable program argument |--debug| for turning on debug info.
Enable program argument |--config| for specifing a module that overrides
the program configs defined in |AoiArgumentParserDeme2.App.Config.Parser.CONFIG_S|
Enable program argument |--default| for specifing a module that overrides
the default values defined in |AoiArgumentParserDeme2.App.Default.Parser.DEFAULT_S|
"""
def __init__(self):
AoiArgumentParser.__init__(self,
debug_arg='--debug',
config_s=Parser.CONFIG_S,
config_arg='--config',
default_s=Default.DEFAULT_S,
default_arg='--default',
)
这是Demo2的Parser设定文件。
__doc__ = """
Module: AoiArgumentParserDeme2.App.Config.Parser
There are two program configs defined, |cars| and |drivers|.
#// program config |cars|
For program config |cars|, we use filter |LoadModule| and |GetAttribute| to
generate the config value. This means, for example, if program argument
|--cars AoiArgumentParserDeme2.App.Config.CarConfig2|
is given at command line, then filter |LoadModule| will load module
|AoiArgumentParserDeme2.App.Config.CarConfig2|.
And then filter |GetAttribute| will get attribute |CAR_S| from the module loaded.
As a result, the value of config |cars| we can get from the parser is not the
module name given at command line, but the attribute ontained from the module loaded.
#// program config |drivers|
For program config |drivers|, we use filter |LoadModule| and |CollectAttributesByIsinstance|
to generate the config value. This means, for example, if program argument
|--drivers AoiArgumentParserDeme2.App.Config.DriverConfig2|
is given at command line, then filter |LoadModule| will load module
|AoiArgumentParserDeme2.App.Config.DriverConfig2|.
And then filter |CollectAttributesByIsinstance| will search the module, for all
classes and object that satisfy the specified interface, which is IDriver in our
case. As a result, the value of config |drivers| we can get from the parser is not
the module name given at command line, but IDriver instances that can be found
from the module loaded.
"""
from AoiU.AoiArgumentParser.FilterLib import LoadModule
from AoiU.AoiArgumentParser.FilterLib import GetAttribute
from AoiU.AoiArgumentParser.FilterLib import CollectAttributesByIsinstance
from AoiArgumentParserDeme2.Func.Interface import IDriver
CONFIG_S = [
dict(argument='--cars',
filter=[
LoadModule(),
GetAttribute('CAR_S'),
]
),
dict(argument='--drivers',
filter=[
LoadModule(),
CollectAttributesByIsinstance(classtype=IDriver),
]
),
]
这是Demo2的默认值设定文件。
__doc__ = """
Module: AoiArgumentParserDeme2.App.Config.Default
"""
DEFAULT_S = dict(
cars='AoiArgumentParserDeme2.App.Config.CarConfig',
drivers='AoiArgumentParserDeme2.App.Config.DriverConfig',
)
这是Demo2的程序文件。它创建一个|ProgramArgumentParser|的对象,在创建完成时|设置|值已经准备就绪了。它像访问这个Parser的attribute一样访问这些|设置|值(cars和drivers)。
__doc__ = """
Module: AoiArgumentParserDeme2.App.Program
This demo shows advanced use of AoiArgumentParser.
It is "advanced" because it uses "filters" to generate config values from program
arguments given at command line, instead of directly using the program arguments.
See doc string of modules
|AoiArgumentParserDeme2.App.ProgramArgumentParser|
|AoiArgumentParserDeme2.App.Config.Parser|
|AoiArgumentParserDeme1.App.Config.Default|
for more details.
#// Command Line Usage:
Program.py
Program.py --cars AoiArgumentParserDeme2.App.Config.CarConfig2
Program.py --drivers AoiArgumentParserDeme2.App.Config.DriverConfig2
"""
from AoiArgumentParserDeme2.App.ProgramArgumentParser import ProgramArgumentParser
def main():
the_parser = ProgramArgumentParser()
for the_car in the_parser.cars:
print(the_car)
for the_driver in the_parser.drivers:
print(the_driver)
if __name__ == '__main__':
main()
跋
此软件已开源到
http://sourceforge.net/p/aoiargparser/ 。