且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

argparse:将参数与另一个参数相关联

更新时间:2023-01-14 13:46:49

基于hpaulj的回答中的代码,我已经调整了实现以使其更加通用.它由 2 个自定义 Action 类组成,ParentActionChildAction.

Based on the code in hpaulj's answer, I've tweaked the implementation to make it more versatile. It consists of 2 custom Action classes, ParentAction and ChildAction.

parser = argparse.ArgumentParser()
# first, create a parent action:
parent = parser.add_argument('-o', '--output', action=ParentAction)
# child actions require a `parent` argument:
parser.add_argument('-f', '--format', action=ChildAction, parent=parent)
# child actions can be customized like all other Actions. For example,
# we can set a default value or customize its behavior - the `sub_action`
# parameter takes the place of the usual `action` parameter:
parser.add_argument('-l', '--list', action=ChildAction, parent=parent,
                    sub_action='append', default=[])

args = parser.parse_args(['-o', 'output1', '-l1', '-l2',
                          '-o', 'output2', '--format', 'csv',
                          '-o', 'output3', '-f1', '-f2'])
print(args)
# output (formatted):
# Namespace(
#     output=OrderedDict([('output1', Namespace(list=['1', '2'])),
#                         ('output2', Namespace(format='csv', list=[])),
#                         ('output3', Namespace(format='2', list=[]))
#                         ])
# )

注意事项

  • 子操作必须始终跟在父操作之后.例如,--format csv -o output1 将不起作用并显示错误消息.
  • 不可能注册多个具有相同名称的 ChildActions,即使是不同的父对象也是如此.

    Caveats

    • Child actions must always follow after parent actions. For example, --format csv -o output1 would not work and show an error message.
    • It's not possible to register multiple ChildActions with the same name, even with different parents.

      示例:

      parent1 = parser.add_argument('-o', '--output', action=ParentAction)
      parser.add_argument('-f', action=ChildAction, parent=parent1)
      parent2 = parser.add_argument('-i', '--input', action=ParentAction)
      parser.add_argument('-f', action=ChildAction, parent=parent2)
      
      # throws exception:
      # argparse.ArgumentError: argument -f: conflicting option string: -f
      

    import argparse
    
    from collections import OrderedDict
    
    
    class ParentAction(argparse.Action):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, default=OrderedDict(), **kwargs)
    
            self.children = []
    
        def __call__(self, parser, namespace, values, option_string=None):
            items = getattr(namespace, self.dest)
            nspace = type(namespace)()
            for child in self.children:
                if child.default is not None:
                    setattr(nspace, child.name, child.default)
            items[values] = nspace
    
    class ChildAction(argparse.Action):
        def __init__(self, *args, parent, sub_action='store', **kwargs):
            super().__init__(*args, **kwargs)
    
            self.dest, self.name = parent.dest, self.dest
            self.action = sub_action
            self._action = None
            self.parent = parent
    
            parent.children.append(self)
    
        def get_action(self, parser):
            if self._action is None:
                action_cls = parser._registry_get('action', self.action, self.action)
                self._action = action_cls(self.option_strings, self.name)
            return self._action
    
        def __call__(self, parser, namespace, values, option_string=None):
            items = getattr(namespace, self.dest)
            try:
                last_item = next(reversed(items.values()))
            except StopIteration:
                raise argparse.ArgumentError(self, "can't be used before {}".format(self.parent.option_strings[0]))
            action = self.get_action(parser)
            action(parser, last_item, values, option_string)