繁体   English   中英

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

[英]Custom parsing function for any number of arguments in 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

应该具有以下命令:

{'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'})

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

使用可变的默认参数(在您的情况下为dict)通常不是一个好主意,请参见此处获取说明:

每次调用该函数时,都要使用默认的arg来表示未提供任何参数,从而创建一个新对象(通常不选择它是一个不错的选择)。

很明显argparse内部将默认值作为结果对象的初始值,因此您不应该在add_argument调用中直接设置默认值,而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()

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

要注意 :这将在对剧本第一次调用才有效。 这是可以接受的,因为parser.parse_args()在脚本中仅应调用一次。

Ancilliary句话:我取下nargs='*' ,因为我觉得它比这里有用的更危险,如果你调用它的方式,并且也去掉了错误的循环values总是使用values

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

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

默认情况下,“ append”确实存在相同的问题。 如果default=['defaultstring'] ,则列表也将从该值开始。 我会使用默认的默认值([]参见下文)解决此问题,并在后期处理中添加默认值(如果列表仍然为空或无)。

关于默认值的注释。 parse_args ,所有操作默认值都会添加到名称空间中(除非名称空间作为parse_args的参数提供)。 然后解析命令行,每个动作对名称空间执行其自身的操作。 最后,所有剩余的字符串默认值都将通过type函数进行转换。

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

_AppendAction类的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_value是在argparse定义的函数。 _copy是它导入的标准copy模块。

_ensure_value作用类似于字典get(key, value, default) ,除了namespace对象外。 在这种情况下,如果self.destNone值(或值为None ),它将返回一个空列表。 因此,它确保追加以列表开头。

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

因此,“追加操作”在call本身中定义了初始的空列表。 并使用copy来避免修改任何其他默认值。

您想要values而不是value吗?

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

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

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

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

在操作中,或将其解析后,可以使用update将它们添加到现有字典中。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM