使用argparse.ArgumentParser()获取分组的结构

时间:2019-11-27 02:46:27

标签: python argparse

我想翻译以下内容:

test.py --group 1 --opt1 foo1 --opt2 bar1 \
        --group 2 --opt1 foo2 \
        --group 3 --opt1 foo3 --opt2 bar3 --opt3 baz3

插入字典(例如命名空间)

{
  "1": {"opt1": "foo1", 
        "opt2": "bar1"},
  "2": {"opt1": "foo2"}, 
  "3": {"opt1": "foo3", 
        "opt2": "bar3",
        "opt3": "baz3"}
}
  • 如果需要,输入可以是其他格式。
  • 如果使用的不是(opt1,opt2,opt3),我想引发一个错误
  • 我应该使用argparse.ArgumentParser()。

你能帮忙吗?

2 个答案:

答案 0 :(得分:2)

似乎是一个很酷的问题-argparse使您可以通过两种方式扩展其回调,或者使用type=...来定制类型,或者使用action=...来定制行为。我认为type=不可能出现上述问题,因此我采用了涉及action=

的解决方案

编写自定义动作的基本方法包括从argparse.Action继承并覆盖__call__(以及可选的__init__

这是实现您的想法的起点,您可能需要扩展/更改它(例如,在操作类中调用super().__call__(...)以处理基本行为(对于类型,等)-我只是用最简单的方法解决了您的问题。

import argparse
import pprint


class GroupAction(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        group, = values
        namespace._current_group = group
        groups = namespace.__dict__.setdefault('groups', {})
        if group in groups:
            raise argparse.ArgumentError(self, f'already specified: {group}')
        groups[group] = {}


class AppendToGroup(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        value, = values
        if not getattr(namespace, '_current_group', None):
            raise argparse.ArgumentError(self, 'outside of group!')
        namespace.groups[namespace._current_group][self.dest] = value


parser = argparse.ArgumentParser()
parser.add_argument('--group', action=GroupAction)
parser.add_argument('--opt1', action=AppendToGroup)
parser.add_argument('--opt2', action=AppendToGroup)
parser.add_argument('--opt3', action=AppendToGroup)
pprint.pprint(parser.parse_args().groups)

请注意,我使用namespace来存储状态,因此不幸的是,最后您会得到一些额外的args._current_group

__dict__.setdefault有点偷偷摸摸,所以我会解释一下-这是一种稍短的书写方式:

if not hasattr(namespace, 'groups'):
    namespace.groups = {}
groups = namespace.groups

我使用的基本策略是存储当前组,并在看到其他参数时追加到该组

这里是样本:

$ python3.7 t.py --group 1 --opt1 a --opt2 b --opt3 c
{'1': {'opt1': 'a', 'opt2': 'b', 'opt3': 'c'}}
$ python3.7 t.py --group 1 --opt1 a --opt2 b --opt3 c --group 2
{'1': {'opt1': 'a', 'opt2': 'b', 'opt3': 'c'}, '2': {}}
$ python3.7 t.py --group 1 --opt1 a --opt2 b --opt3 c --group 2 --opt3 c
{'1': {'opt1': 'a', 'opt2': 'b', 'opt3': 'c'}, '2': {'opt3': 'c'}}
$ python3.7 t.py --group 1 --group 1
usage: t.py [-h] [--group GROUP] [--opt1 OPT1] [--opt2 OPT2] [--opt3 OPT3]
t.py: error: argument --group: already specified: 1
$ python3.7 t.py --opt1 a --group 1
usage: t.py [-h] [--group GROUP] [--opt1 OPT1] [--opt2 OPT2] [--opt3 OPT3]
t.py: error: argument --opt1: outside of group!

答案 1 :(得分:-1)

这是我能想到的最好的方法,希望这就是您想要的。

您需要以代码底部显示的形式提供参数,但您在帖子中提到了这一点。

即,使用python3 script.py --group 1=2=3 --opt1 foo1=foo2=foo3 --opt2 bar1=None=bar3 --opt3 None=None=baz3重现我的结果。

import argparse, copy
parser = argparse.ArgumentParser()
parser.add_argument("--group", default='1')
parser.add_argument("--opt1", default='foo1')
parser.add_argument("--opt2", default='bar1')
parser.add_argument("--opt3", default='baz3')
args = parser.parse_args()

def splitter(arg):
    return arg.split("=")

groups = splitter(args.group) if "=" in args.group else args.group
opt1 = splitter(args.opt1) if "=" in args.opt1 else args.opt1
opt2 = splitter(args.opt2) if "=" in args.opt2 else args.opt2
opt3 = splitter(args.opt3) if "=" in args.opt3 else args.opt3

# Dictionary with no values corresponding to just the keys in "group"
group_dict = dict.fromkeys(groups)
final_dict = copy.deepcopy(group_dict)

# Add dictionary inside each key - will remove blank keys later.
for key in group_dict:
    group_dict[key] = dict.fromkeys(['opt1', 'opt2', 'opt3'])

# Now each group_dict has form
# {'3': {'opt2': None, 'opt3': None, 'opt1': None}, '2': {'opt2': None, 'opt3': None, 'opt1': None}, '1': {'opt2': None, 'opt3': None, 'opt1': None}}
# Must use group_dict['1']['opt3'] to access 1 -> opt3 -> value
for i, key in enumerate(groups):
    group_dict[key]["opt1"] = opt1[i]
    group_dict[key]["opt2"] = opt2[i]
    group_dict[key]["opt3"] = opt3[i]

    # Remove None elements - key is main dict - key2 & value are sub dict
    final_dict[key] = {key2: value for key2, value in group_dict[key].items() if value != 'None'}

print(final_dict)

输出:

{'1': {'opt1': 'foo1', 'opt2': 'bar1'}, '2': {'opt1': 'foo2'}, '3': {'opt2': 'bar3', 'opt3': 'baz3', 'opt1': 'foo3'}}

摘要:

  1. 如果需要,输入可以是其他格式。 (检查)

    使用类似于python3 script.py --group 1=2=3 --opt1 foo1=foo2=foo3 --opt2 bar1=None=bar3 --opt3 None=None=baz3

  2. 的格式
  3. 如果使用的不是(opt1,opt2,opt3),我想提出一个错误。 (检查)

    默认情况下,使用argparse会发生这种情况,您可以改为使用--opt4来尝试。

  4. 我应该使用argparse.ArgumentParser()。 (检查)