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

2024-07-03 06:50:22 发布

您现在位置:Python中文网/ 问答频道 /正文

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

./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中,如下所示:

^{pr2}$

此外,如果没有提供值,则应该提供一个默认值。像

./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'})

但是,当我添加参数时,它们被附加到默认值中。我的期望是将它们添加到一个空的dict中:

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

怎样才能得到我想要的东西?在


Tags: pyselfaddparserfor参数netargparse
3条回答

使用可变的默认参数(在您的例子中是dict)通常不是一个好主意,请参见here了解解释:

Create a new object each time the function is called, by using a default arg to signal that no argument was provided (None is often a good choice).

我解决这个问题的第一种方法是使用action='append',并在解析后将结果列表转换为字典。代码量也差不多。在

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

关于违约的说明。在parse_args的开头,所有操作默认值都被添加到名称空间中(除非名称空间作为parse_args的参数给定)。然后解析命令行,每个操作对命名空间执行自己的操作。最后,使用type函数转换任何剩余的字符串默认值。在

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

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

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.dest没有值(或者值是None),则返回一个空列表。因此,它确保append以一个列表开始。在

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

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

你想要values而不是{}?在

^{pr2}$

我倾向于把这个转换放在一个类型函数中,例如

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

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

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

很明显,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处理可变默认参数的方式:

^{pr2}$

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

但是注意:这只在第一次调用脚本时有效。这里可以接受,因为parser.parse_args()在脚本中只应调用一次。在

附带说明:我删除了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

相关问题 更多 >