我有一个脚本通过命令行获取命名参数。可以多次提供一个参数。例如,我想运行一个脚本:
./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'})
达到我需要的正确方法是什么?
答案 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_value
是argparse
中定义的函数。 _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
将这些内容添加到现有的词典中。