argparse - Combining parent parser, subparsers and default values

后端 未结 2 1692
长情又很酷
长情又很酷 2020-12-10 12:32

I wanted to define different subparsers in a script, with both inheriting options from a common parent, but with different defaults. It doesn\'t work as expected, though.

2条回答
  •  爱一瞬间的悲伤
    2020-12-10 13:14

    What's happening

    The problem here is that parser arguments are objects, and when a parser inherits from it's parents, it adds a reference to the parent's action to it's own list. When you call set_default, it sets the default on this object, which is shared across the subparsers.

    You can examine the subparsers to see this:

    >>> a1 = [ action for action in subparser1._actions if action.dest=='n' ].pop()
    >>> a2 = [ action for action in subparser2._actions if action.dest=='n' ].pop()
    >>> a1 is a2 # same object in memory
    True
    >>> a1.default
    20
    >>> type(a1)
    
    

    First solution: Explicitly add this argument to each subparser

    You can fix this by adding the argument to each subparser separately rather than adding it to the base class.

    subparser1= subparsers.add_parser('a', help='subparser 1', 
                                   parents=[base_parser])
    subparser1.add_argument('-n', help='number', type=int, default=50)
    subparser2= subparsers.add_parser('b', help='subparser 2', 
                                   parents=[base_parser])
    subparser2.add_argument('-n', help='number', type=int, default=20)
    ...
    

    Second solution: multiple base classes

    If there are many subparsers which share the same default value, and you want to avoid this, you can create different base classes for each default. Since parents is a list of base classes, you can still group the common parts into another base class, and pass the subparser multiple base classes to inherit from. This is probably unnecessarily complicated.

    import argparse
    
    # this is the top level parser
    parser = argparse.ArgumentParser(description='bla bla')
    
    # this serves as a parent parser
    base_parser = argparse.ArgumentParser(add_help=False)
    # add common args
    
    # for group with 50 default
    base_parser_50 = argparse.ArgumentParser(add_help=False)
    base_parser_50.add_argument('-n', help='number', type=int, default=50)
    
    # for group with 50 default
    base_parser_20 = argparse.ArgumentParser(add_help=False)
    base_parser_20.add_argument('-n', help='number', type=int, default=20)
    
    # subparsers
    subparsers = parser.add_subparsers()
    subparser1= subparsers.add_parser('a', help='subparser 1', 
                                       parents=[base_parser, base_parser_50])
    
    subparser2 = subparsers.add_parser('b', help='subparser 2',
                                       parents=[base_parser, base_parser_20])
    
    args = parser.parse_args()
    print args
    

    First solution with shared args

    You can also share a dictionary for the arguments and use unpacking to avoid repeating all the arguments:

    import argparse
    
    # this is the top level parser
    parser = argparse.ArgumentParser(description='bla bla')
    
    n_args = '-n',
    n_kwargs = {'help': 'number', 'type': int}
    
    # subparsers
    subparsers = parser.add_subparsers()
    subparser1= subparsers.add_parser('a', help='subparser 1')
    subparser1.add_argument(*n_args, default=50, **n_kwargs)
    
    subparser2 = subparsers.add_parser('b', help='subparser 2')
    subparser2.add_argument(*n_args, default=20, **n_kwargs)
    
    args = parser.parse_args()
    print args
    

提交回复
热议问题