子类化argparse参数解析器
我正在尝试写一个解析器类,这个类是从Python的argparse库中的ArgumentParser类派生出来的。下面的代码在命令行上运行得很好,但在我的模块中却出现了一个我很难理解的错误。
这段代码(我稍微删减了一些不重要的部分)如下:
class SansParser(argparse.ArgumentParser):
"""Argument parser for preparing a SansModel fit or calculation
"""
def __init__(self):
"""Initialisation method for the parser class"""
argparse.ArgumentParser.__init__(self)
# Subparsers for the two commands 'calc' and 'fit'
self.subparsers = self.add_subparsers()
self.fit_parser = self.subparsers.add_parser('fit', help="Fit a dataset")
self.fit_parser.add_argument('-d', '-data', '-dataset', type = str,
dest = 'dataset',
help = "The dataset to fit in SasXML format")
self.fit_parser.set_defaults(func=fit)
self.calc_parser = self.subparsers.add_parser('calc', prog='test')
self.calc_parser.set_defaults(func=calculate)
我可以把这段代码当作脚本运行,没问题。如果我在命令行中运行它,或者在Python命令行中导入它并尝试实例化这个类,我就会得到:
$ python sansmodel.py
Traceback (most recent call last):
File "sansmodel.py", line 57, in <module>
parser = SansParser()
File "sansmodel.py", line 41, in __init__
self.fit_parser = self.subparsers.add_parser('fit', help="Fit a dataset")
File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/argparse.py",
line 1064, in add_parser
parser = self._parser_class(**kwargs)
TypeError: __init__() got an unexpected keyword argument 'prog'
据我所知,argparse库中的代码在1064行明确创建了'prog'这个关键字,这应该是预期的行为,所以我对哪里出现了意外感到困惑。我在想,可能是我在作用域方面搞错了什么?
3 个回答
今天下午我也遇到了同样的错误,找解决办法的时候看到了你的问题。
如果你看过ArgumentParser的__init__()
方法,就会发现它接受一系列参数,其中就包括'prog'这个参数:
class ArgumentParser(_AttributeHolder, _ActionsContainer):
"""SOME DOC STRING..."""
def __init__(self,
prog=None,
usage=None,
description=None,
epilog=None,
parents=[],
formatter_class=HelpFormatter,
prefix_chars='-',
fromfile_prefix_chars=None,
argument_default=None,
conflict_handler='error',
add_help=True,
allow_abbrev=True):
...... # SOME IMPLEMENTATION
我认为问题在于:自定义的解析器类重写了__init__()
方法,并且没有接受任何参数。但其他的方法没有被修改,这就导致了方法之间的行为冲突。当创建子解析器时,add_parser()会调用解析器的__init__()
,并传入包括'prog'在内的参数。对于ArgumentParser来说,这没问题,但对于重写了__init__()
的自定义解析器来说,显然会出错。
当然,@jcollado的建议是有效的,但似乎也取消了子解析器行为的自定义。
我解决这个问题的方法有点笨,但也能很好地工作。当重写ArgumentParser的__init__()
时,只需保留每个参数及其默认值。像这样:
class MyParser(argparse.ArgumentParser):
def __init__(
self,
prog=None,
usage=None,
description=None,
epilog=None,
parents=[],
formatter_class=argparse.HelpFormatter,
prefix_chars='-',
fromfile_prefix_chars=None,
argument_default=None,
conflict_handler='error',
add_help=True,
allow_abbrev=True
# and your custom arguments here
):
super(MyParser, self).__init__(prog=prog, usage=usage, description=description, epilog=epilog,
parents=parents, formatter_class=formatter_class,
prefix_chars=prefix_chars, fromfile_prefix_chars=fromfile_prefix_chars,
argument_default=argument_default, conflict_handler=conflict_handler,
add_help=add_help, allow_abbrev=allow_abbrev
)
# and your custom actions here
我同意@jcollado的建议,直接给ArgumentParser
对象添加参数,而不是去创建一个子类。
不过,如果你确实要创建子类,我建议你修改一下__init__
方法的签名,而不是去改self.subparsers._parser_class
的值。
class SansParser(argparse.ArgumentParser):
"""Argument parser for preparing a SansModel fit or calculation
"""
def __init__(self, *args, **kwargs):
"""Initialisation method for the parser class"""
if 'my_keyword' in kwargs:
# Do what needs to be done with kwargs['my_keyword']
del kwargs['my_keyword'] # Since ArgumentParser won't recognize it
argparse.ArgumentParser.__init__(self, *args, **kwargs)
这样一来,你的子类就能和ArgumentParser
一样工作,除了你自己修改的地方。
除非你在修改一些 argparse.ArgumentParser
的行为,否则我建议你创建一个解析器对象,并把参数和子解析器添加到这个对象里。
话说回来,问题在于,当你添加一个新的解析器时,SansParser
实现中重写的 __init__
方法并不接受和原来的 ArgumentParser
一样的参数。
解决这个问题的方法可以是这样的:
self.subparsers._parser_class = argparse.ArgumentParser
这样,当调用 add_parser
时,不会创建一个新的 SansParser
(因为那样会导致无限递归出错),而是会创建一个新的 ArgumentParser
。