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 操作,它将解析每个参数并将结果存储在一个字典中,例如:
{ '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'})
达到我需要的正确方法是什么?
使用可变默认参数(在您的情况下是字典)通常不是一个好主意,请参阅 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).
很明显 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()
这样,如果选项存在,可选的默认值将被清除。
但是注意:这仅在第一次调用脚本时有效。这里是可以接受的,因为 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
我解决这个问题的第一个方法是使用action='append'
,并在解析后将结果列表变成字典。代码量会差不多。
'append' 确实存在与默认值相同的问题。如果 default=['defaultstring']
,则列表也将以该值开头。我会通过使用默认默认值([] 见下文)来解决这个问题,并在 post 处理中添加默认值(如果列表仍然为空或 None)。
关于默认值的说明。在 parse_args
的开头,所有操作默认值都添加到名称空间(除非名称空间作为参数提供给 parse_args
)。然后解析命令行,每个动作对命名空间做自己的事情。最后,任何剩余的字符串默认值都使用 type
函数进行转换。
在您的情况下,namespace.user_nets[location] = subnet
找到 user_nets
属性,并添加新条目。该属性默认初始化为字典,因此默认值出现在最终字典中。事实上,如果您将默认值保留为 None
或某个字符串,您的代码将无法工作。
call
对 _AppendAction
class 可能有指导意义:
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
),则它 returns 是一个空列表。所以它确保追加以列表开头。
_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}
这也是进行错误检查的好地方。
在操作中,或 post 解析这些可以添加到现有词典 update
。
我有一个通过命令行获取命名参数的脚本。可以多次提供其中一个参数。例如我想 运行 一个脚本:
./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 操作,它将解析每个参数并将结果存储在一个字典中,例如:
{ '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'})
达到我需要的正确方法是什么?
使用可变默认参数(在您的情况下是字典)通常不是一个好主意,请参阅 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).
很明显 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()
这样,如果选项存在,可选的默认值将被清除。
但是注意:这仅在第一次调用脚本时有效。这里是可以接受的,因为 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
我解决这个问题的第一个方法是使用action='append'
,并在解析后将结果列表变成字典。代码量会差不多。
'append' 确实存在与默认值相同的问题。如果 default=['defaultstring']
,则列表也将以该值开头。我会通过使用默认默认值([] 见下文)来解决这个问题,并在 post 处理中添加默认值(如果列表仍然为空或 None)。
关于默认值的说明。在 parse_args
的开头,所有操作默认值都添加到名称空间(除非名称空间作为参数提供给 parse_args
)。然后解析命令行,每个动作对命名空间做自己的事情。最后,任何剩余的字符串默认值都使用 type
函数进行转换。
在您的情况下,namespace.user_nets[location] = subnet
找到 user_nets
属性,并添加新条目。该属性默认初始化为字典,因此默认值出现在最终字典中。事实上,如果您将默认值保留为 None
或某个字符串,您的代码将无法工作。
call
对 _AppendAction
class 可能有指导意义:
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
),则它 returns 是一个空列表。所以它确保追加以列表开头。
_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}
这也是进行错误检查的好地方。
在操作中,或 post 解析这些可以添加到现有词典 update
。