Python argparse中任意数量参数的自定义解析函数

时间:2015-10-23 11:14:59

标签: python argparse

我有一个脚本通过命令行获取命名参数。可以多次提供一个参数。例如,我想运行一个脚本:

./script.py --add-net=user1:10.0.0.0/24 --add-net=user2:10.0.1.0/24 --add-net=user3:10.0.2.0/24

现在我想要一个argparse操作来解析每个参数并将结果存储在dict中:

{ 'user1': '10.0.0.0/24',
  'user2': '10.0.1.0/24',
  'user3': '10.0.2.0/24' }

如果没有提供任何值,也应该提供默认值。像

./script.py

应该像dict一样:

{'user': '192.168.0.0/24'}

我相信我必须为argparse构建自定义操作。我想出的是:

class ParseIPNets(argparse.Action):
    """docstring for ParseIPNets"""
    def __init__(self, option_strings, dest, nargs=None, **kwargs):
        super(ParseIPNets, self).__init__(option_strings, dest, **kwargs)

    def __call__(self, parser, namespace, values, option_string=None):
        for value in values:
            location, subnet = values.split(':')
            namespace.user_nets[location] = subnet

parser = argparse.ArgumentParser(description='foo')
parser.add_argument('--add-net',
                    nargs='*',
                    action=ParseIPNets,
                    dest='user_nets',
                    help='Nets subnets for users. Can be used multiple times',
                    default={"user1": "198.51.100.0/24"})

args = parser.parse_args()

当我需要使用默认值时,它可以正常工作:

test.py
Namespace(user_nets={'user1': '198.51.100.0/24'})

然而,当我添加参数时 - 它们被附加到默认值。我的期望是他们应该被添加到一个空的字典中:

test.py --add-net=a:10.0.0.0/24 --add-net=b:10.1.0.0/24
Namespace(user_nets={'a': '10.0.0.0/24', 'b': '10.1.0.0/24', 'user1': '198.51.100.0/24'})

达到我需要的正确方法是什么?

3 个答案:

答案 0 :(得分:1)

使用可变默认参数(在您的情况下为dict)通常不是一个好主意,请参阅here获取解释:

  

每次调用函数时,使用a创建一个新对象   默认arg表示没有提供参数(None通常是   不错的选择)。

答案 1 :(得分:1)

很明显argparse在内部将默认值作为结果对象的初始值,您不应该直接在add_argument调用中设置默认值,而是进行一些额外的处理:

parser.add_argument('--add-net',
                    action=ParseIPNets,
                    dest='user_nets',
                    help='Nets subnets for users. Can be used multiple times',
                    default = {})

args = parser.parse_args()
if len(args.user_nets) == 0:
    args.user_nets['user1'] = "198.51.100.0/24"

或者,如果您想获得更好的用户体验,可以使用Python处理可变默认参数的方式:

class ParseIPNets(argparse.Action):
    """docstring for ParseIPNets"""
    def __init__(self, option_strings, dest, nargs=None, **kwargs):
        super(ParseIPNets, self).__init__(option_strings, dest, **kwargs)
    def __call__(self, parser, namespace, values, option_string=None, first=[True]):
        if first[0]:
            namespace.user_nets.clear()
            first[0] = False
        location, subnet = values.split(':')
        namespace.user_nets[location] = subnet

parser.add_argument('--add-net',
                    action=ParseIPNets,
                    dest='user_nets',
                    help='Nets subnets for users. Can be used multiple times',
                    default={"user1": "198.51.100.0/24"})

args = parser.parse_args()

这样,如果存在该选项,则会清除可选的默认值。

但是 BEWARE :这只会在首次调用脚本时起作用。这是可以接受的,因为parser.parse_args()只应在脚本中调用一次。

辅助评论:我删除了nargs='*',因为如果你这样称呼它,我觉得它比有用的更危险,并且还总是使用values删除values上的错误循环:< / p>

test.py --add-net=a:10.0.0.0/24 --add-net=b:10.1.0.0/24

nargs='*'对于以下语法有意义:

test.py --add-net a:10.0.0.0/24 b:10.1.0.0/24

,代码为:

    def __call__(self, parser, namespace, values, option_string=None, first=[True]):
        if first[0]:
            namespace.user_nets.clear()
            first[0] = False
        for value in values:
            location, subnet = value.split(':')
            namespace.user_nets[location] = subnet

答案 2 :(得分:0)

我遇到此问题的第一种方法是使用action='append',并在解析后将结果列表转换为字典。代码量相似。

'append'确实存在与默认值相同的问题。如果default=['defaultstring'],则列表也将以该值开头。我通过使用默认默认值([]见下文)来解决这个问题,并在后期处理中添加默认值(如果列表仍为空或无)。

关于默认值的说明。在parse_args开始时,所有操作默认值都将添加到命名空间(除非将命名空间作为参数提供给parse_args)。然后解析命令行,每个动作都对命名空间做自己的事情。最后,使用type函数转换任何剩余的字符串默认值。

在您的情况下namespace.user_nets[location] = subnet找到user_nets属性,并添加新条目。该属性默认初始化为字典,因此默认值出现在最终字典中。实际上,如果您将默认值保留为None或某个字符串,则您的代码将无效。

call课程的_AppendAction可能具有指导意义:

def __call__(self, parser, namespace, values, option_string=None):
    items = _copy.copy(_ensure_value(namespace, self.dest, []))
    items.append(values)
    setattr(namespace, self.dest, items)

_ensure_valueargparse中定义的函数。 _copy是导入的标准copy模块。

除了_ensure_value对象外,

get(key, value, default)的行为类似于字典namespace。在这种情况下,如果还没有self.dest的值(或值为None),则返回空列表。因此,它确保追加以列表开头。

_copy.copy确保将值附加到副本。这样,parse_args将不会修改default。它避免了@miles82注意到的问题。

所以'append action'定义了call本身的初始空列表。并使用copy来避免修改任何其他默认值。

您想要values而不是value吗?

location, subnet = values.split(':')

我倾向于将此转换置于类型函数中,例如

def dict_type(astring):
   key, value = astring.split(':')
   return {key:value}

这也是进行错误检查的好地方。

在动作或解析后,可以使用update将这些内容添加到现有的词典中。