子类化argparse参数解析器

3

我正在尝试编写一个解析器类,该类派生自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)

我可以将这个脚本等效地运行,没有问题。但是如果我从shell或者在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个回答

6

除非你需要重写一些argparse.ArgumentParser的行为,我建议创建一个解析器对象并将参数和子解析器添加到该对象中。

话虽如此,问题在于当添加一个新解析器时,由SansParser实现覆盖的__init__方法不能像原始的ArgumentParser那样接受相同的参数。

解决该问题的方法应该是这样的:

self.subparsers._parser_class = argparse.ArgumentParser

这样,当调用add_parser时,不会创建一个新的SansParser(由于无限递归而失败),而是创建一个新的ArgumentParser

1
没错,这个方法可行。我相信我之前只是对问题的思考方式有误。现在看来,更合理的做法是构建一个应用程序类,并将解析器作为对象引入其中,因为正如你所说,我不需要覆盖任何方法。 - Cameron Neylon

1
我今天下午遇到了同样的错误,在寻找解决方案时发现了你的问题。
如果您已经阅读了 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()会使用包括“prog”在内的参数调用解析器的__init__()。对于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

1

我同意@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 一样工作,除了在您重写其行为的地方。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接