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

时间:2018-01-29 15:33:56

标签: python argparse

使用argparse,可以重复一个参数并将所有值收集到一个列表中:

parser = ArgumentParser()
parser.add_argument('-o', '--output', action='append')

args = parser.parse_args(['-o', 'output1', '-o', 'output2'])
print(vars(args))
# {'output': ['output1', 'output2']}

我正在寻找一种方法来将标志与每个参数相关联,以便可以这样做:

args = parser.parse_args(['-o', 'output1', '--format', 'text',
                          '-o', 'output2', '--format', 'csv'])

获得这样的输出(或类似的东西):

{'output': {'output1': {'format': 'text'},
            'output2': {'format': 'csv'}
           }
}

理想情况下,这些标志应遵循通常的语义 - 例如,--format可以是可选的,或者可能有多个与每个-o输出关联的参数,在这种情况下它们应该可以在任何订单(即-o output1 -a -b -c应相当于-o output1 -c -a -b)。

这可以用argparse来完成吗?

3 个答案:

答案 0 :(得分:4)

3个解析器在一组-o-f标志上运行:

简单追加 - 2 dest

之间没有联系
import argparse

parser = argparse.ArgumentParser()
parser.add_argument('-o', '--output', action='append')
parser.add_argument('-f', '--format', action='append')

args = parser.parse_args(['-o', 'output1', '-o', 'output2'])
print(args)
args = parser.parse_args(['-o', 'output1', '--format', 'text',
                          '-o', 'output2', '--format', 'csv'])
print(args)

args = parser.parse_args(['-o', 'output1',
                          '-o', 'output2', '--format', 'csv',
                          '-o', 'output3', '-f1', '-f2'])
print(args)
print()

nargs='+';将参数保持在一起,但不使用format标志:

parser = argparse.ArgumentParser()
parser.add_argument('-o', '--output', action='append', nargs='+')
#parser.add_argument('-f', '--format', action='append')

args = parser.parse_args(['-o', 'output1', '-o', 'output2'])
print(args)
args = parser.parse_args(['-o', 'output1', 'text',
                          '-o', 'output2', 'csv'])
print(args)

args = parser.parse_args(['-o', 'output1',
                          '-o', 'output2', 'csv',
                          '-o', 'output3', '1', '2'])
print(args)
print()

append类派生的自定义类。为每个output创建一个字典。 format修改了最后一个output字典:

class Foo1(argparse._AppendAction):
    def __call__(self, parser, namespace, values, option_string=None):
        items = argparse._copy.copy(argparse._ensure_value(namespace, self.dest, []))
        dd = {'output': values, 'format': []}
        items.append(dd)
        setattr(namespace, self.dest, items)

class Foo2(argparse._AppendAction):
    def __call__(self, parser, namespace, values, option_string=None):
        items = argparse._copy.copy(argparse._ensure_value(namespace, self.dest, []))
        last = items[-1]   # error if -f before -o
        last['format'].append(values)
        setattr(namespace, self.dest, items)

parser = argparse.ArgumentParser()
parser.add_argument('-o', '--output', action=Foo1)
parser.add_argument('-f', '--format', action=Foo2, dest='output')

args = parser.parse_args(['-o', 'output1', '-o', 'output2'])
print(args)
args = parser.parse_args(['-o', 'output1', '--format', 'text',
                          '-o', 'output2', '--format', 'csv'])
print(args)

args = parser.parse_args(['-o', 'output1',
                          '-o', 'output2', '--format', 'csv',
                          '-o', 'output3', '-f1', '-f2'])
print(args)
print()

产生

1238:~/mypy$ python stack48504770.py 
Namespace(format=None, output=['output1', 'output2'])
Namespace(format=['text', 'csv'], output=['output1', 'output2'])
Namespace(format=['csv', '1', '2'], output=['output1', 'output2', 'output3'])

Namespace(output=[['output1'], ['output2']])
Namespace(output=[['output1', 'text'], ['output2', 'csv']])
Namespace(output=[['output1'], ['output2', 'csv'], ['output3', '1', '2']])

Namespace(output=[{'output': 'output1', 'format': []}, 
                  {'output': 'output2', 'format': []}])
Namespace(output=[{'output': 'output1', 'format': ['text']}, 
                  {'output': 'output2', 'format': ['csv']}])
Namespace(output=[{'output': 'output1', 'format': []}, 
                  {'output': 'output2', 'format': ['csv']}, 
                  {'output': 'output3', 'format': ['1', '2']}])
()

答案 1 :(得分:2)

根据hpaulj's answer中的代码,我调整了实施,使其更具通用性。它由2个自定义操作类ParentActionChildAction组成。

用法

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。

    示例:

    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)

答案 2 :(得分:1)

只需定义-o即可获得两个参数。

parser.add_argument('-o', '--output', nargs=2, action='append')

然后-o output1 text -o output2 csv

会产生类似

的东西
args = p.parse_args(['-o', 'output1', 'text', '-o', 'output2', 'csv'])
assert args.output == [['output1', 'text'], ['output2', 'csv']]

您可以对args.output进行后处理以获得所需的dict,或者定义Action方法将自行构建__call__值的自定义dict