问题
I am trying to write a parser class derived from the Python argparse ArgumentParser class. The outlines of the following code work fine on the command line but generate an error I am struggling to understand in the context of my module.
The code (stripped down a little to remove the unimportant stuff) is as follows:
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)
I can run the equivalent of this as a script and its fine. If I run it from either shell or import into python command line and try to instantiate the class I get:
$ 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'
As far as I can tell the code in argparse itself at line 1064 explicitly creates the 'prog' keyword and this is the expected behaviour so I'm confused as to where it is unexpected. I'm guessing I've got something backwards with the scope somehow?
回答1:
Unless you're overwritting some argparse.ArgumentParser
behaviour, I recommend to create a parser object and add the arguments and the subparsers to that object.
That said, the problem is that when adding a new parser the __init__
method, that has been overwritten by the SansParser
implementation, doesn't accept the same arguments as the original ArgumentParser
.
A workaround to the problem should be this one:
self.subparsers._parser_class = argparse.ArgumentParser
This way, when add_parser
is called, instead of creating a new SansParser
(that would fail because of infinite recursion), a new ArgumentParser
will be created.
回答2:
I agree with @jcollado's suggestion to simply add arguments to an ArgumentParser
object, rather than subclass.
However, if you do subclass, I would recommend you change the signature of your __init__
method rather than change the value of 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)
This way, your subclass can work the same way as ArgumentParser
, except for where you override its behavior.
回答3:
I got the same error this afternoon, and found your question when looking for some solution.
If you have read the ArgumentParser's __init__()
, you could see that it accepts a series of arguments, including right the '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
I think the fact is: the custom parser class overridden the __init__()
method, and accepts no argument. But other methods are not modified. This made the methods' behaviors conflict. When creating sub parser, add_parser() calls the parser's __init__()
with arguments including 'prog'. To ArgumentParser, it's ok, but to the custom parser with overridden __init__()
, obviously it will fail.
Of course @jcollado's suggestion works fine, but seems it also cancels the customization of sub parser's behavior.
My solution to this problem, a little ugly but also works well. When overriding the __init__()
of ArgumentParser, just keep every argument and its default value. Like this:
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
来源:https://stackoverflow.com/questions/8757338/sub-classing-the-argparse-argument-parser