argparse:将用户输入映射到定义的常量
argparse: map user input to defined constant
我想构建一个 parser.add_argument(...)
以将给定参数映射到我的代码中定义的常量。
假设我有以下
import argparse
# Both are the same type
CONST_A = <something>
CONST_B = <otherthing>
parser = argparse.ArgumentParser()
parser.add_argument(...)
# I'd like the following to be true:
parser.parse_args("--foo A".split()).foo == CONST_A
parser.parse_args("--foo B".split()).foo == CONST_B
我可以用什么代替 ...
?
我能用 const
做的最好的事情是:
import argparse
# Both are the same type
CONST_A = 10
CONST_B = 20
parser = argparse.ArgumentParser()
status_group = parser.add_mutually_exclusive_group(required=True)
status_group.add_argument("-a", const=CONST_A, action='store_const')
status_group.add_argument("-b", const=CONST_B, action='store_const')
# I'd like the following to be true:
print parser.parse_args("-a".split()).a == CONST_A # True
print parser.parse_args("-b".split()).b == CONST_B # True
请注意,常量保存在两个不同的属性 a
和 b
中, 不适合我:(
这是一个有趣的问题。据我所知,argparse
不直接支持此功能。
如果您发现这种模式经常出现,您可以编写一个小实用程序 class 来为您完成此操作,transforming args
into a dictionary via vars
:
class Switcher(object):
def __init__(self, d):
self._d = d
def __call__(self, args):
args_ = vars(args)
for k, v in self._d.items():
if args_[k]:
return v
您可以按如下方式使用。假设您的解析器定义为:
import argparse
parser = argparse.ArgumentParser()
g = parser.add_mutually_exclusive_group()
g.add_argument('-a', action='store_true', default=False)
g.add_argument('-b', action='store_true', default=False)
然后你可以定义一个Switcher
通过:
s = Switcher({'a': 10, 'b': 20})
并像这样使用它:
>>> print s(parser.parse_args(['-a']))
10
>>> print s(parser.parse_args(['-b']))
20
最简单的方法是利用 add_argument
中的 type=
选项,就像 所做的那样,尽管它可以用工厂函数概括:
def argconv(**convs):
def parse_argument(arg):
if arg in convs:
return convs[arg]
else:
msg = "invalid choice: {!r} (choose from {})"
choices = ", ".join(sorted(repr(choice) for choice in convs.keys()))
raise argparse.ArgumentTypeError(msg.format(arg,choices))
return parse_argument
然后用 type=argconv(A=CONST_A, B=CONST_B)
:
代替 ...
parser.add_argument("--foo", type=argconv(A=CONST_A, B=CONST_B))
然后在您的示例中一切都会按照您希望的方式运行。
以下是我发布的第一个答案,它仍然有效,但不像上面的解决方案那么简单。
另一种方法是创建一个继承自 argparse.ArgumentParser
的 class 并覆盖 parse_args
以修改生成的结果:
import argparse
class MappedParser(argparse.ArgumentParser):
mapping = {} #backup if you don't use def_mapping
def def_mapping(self,**options):
self.mapping = options
def parse_args(self,args=None,namespace=None):
result = argparse.ArgumentParser.parse_args(self,args,namespace)
for name,options in self.mapping.items(): #by default this is is empty so the loop is skipped
if name in result:
key = getattr(result,name)
if key in options:
replace_with = options[key]
setattr(result,name,replace_with)
else:
self.error("option {name!r} got invalid value: {key!r}\n must be one of {valid}".format(name=name,key=key,valid=tuple(options.keys())))
return #error should exit program but I'll leave this just to be safe.
return result
这样,您的(示例)程序的其余部分将如下所示:
# There is nothing restricting their type.
CONST_A = "<something>"
CONST_B = ["other value", "type is irrelevent"]
parser = MappedParser() #constructor is same
parser.def_mapping(foo={"A":CONST_A, "B":CONST_B})
parser.add_argument("--foo") # and this is unchanged
# the following is now true:
print(parser.parse_args("--foo A".split()).foo is CONST_A)
print(parser.parse_args("--foo B".split()).foo is CONST_B)
#note that 'is' operator works so it is even the same reference
#this gives decent error message
parser.parse_args("--foo INVALID".split())
print("when parser.error() is called the program ends so this never is printed")
像这样添加额外的选项:
parser.def_mapping(foo={"A":CONST_A, "B":CONST_B,"C":"third option"})
或像这样的额外参数:
parser.def_mapping(foo={"A":CONST_A, "B":CONST_B},
conv={"int":int,"float":float})
以及任何未在 def_mapping
中指定的添加参数都被保留,因此很容易实现。
我不会创建新的动作或对象:只需使用 dict
来存储常量,用 choices
限制 --foo
的值并使用 parsed_args.foo
索引 dict
:
import argparse
CONST = {'A': 'something',
'B': 'otherthing'}
parser = argparse.ArgumentParser()
parser.add_argument('--foo', choices=('A', 'B'))
assert CONST[parser.parse_args("--foo A".split()).foo] == 'something'
assert CONST[parser.parse_args("--foo B".split()).foo] == 'otherthing'
choices
和 dict
键的组合基本上修复了 foo
的结果。
如果你想让事情变得更清晰或更简单,你可以在解析参数后将 foo
重新分配给你的 dict
值:
args = parser.parse_args("--foo A".split())
args.foo = CONST[args.foo]
现在 args.foo
直接等于 'something'
。
这对于自定义 type
参数来说是一个很好的例子:
CONST_A='<A>'
CONST_B='<B>'
def footype(astring):
dd = {'A':CONST_A, 'B':CONST_B}
try:
return dd[astring]
except KeyError:
raise argparse.ArgumentTypeError('enter A or B')
parser = argparse.ArgumentParser()
parser.add_argument('--foo', type=footype)
它会产生一个类似
的命名空间
Namespace(foo='<A>')
和错误消息(如果给定 --foo C
)如:
usage: stack35648071.py [-h] [--foo FOO]
stack35648071.py: error: argument --foo: enter A or B
我尝试添加 choices
但帮助消息不正确。使用 metavar
和 help
来指导您的用户。
根据 docs:
The action keyword argument specifies how the command-line arguments should be handled. You may also specify an arbitrary action by passing an Action subclass or other object that implements the same interface.
In [5]: import argparse
...:
...: class MappingAction(argparse.Action):
...: def __init__(self, option_strings, dest, mapping, **kwargs):
...: self.mapping = mapping
...: super(MappingAction, self).__init__(option_strings, dest, **kwargs)
...:
...: def __call__(self, parser, namespace, values, option_string=None):
...: values = self.mapping.get(values, None)
...: setattr(namespace, self.dest, values)
...:
In [6]: parser = argparse.ArgumentParser()
...: mapping = {
...: 'A': '<something>',
...: 'B': '<otherthing>',
...: }
...: parser.add_argument('bar', action=MappingAction, choices=mapping.keys(), mapping=mapping)
In [7]: args = parser.parse_args('A'.split())
In [8]: args.bar
Out[8]: '<something>'
我想构建一个 parser.add_argument(...)
以将给定参数映射到我的代码中定义的常量。
假设我有以下
import argparse
# Both are the same type
CONST_A = <something>
CONST_B = <otherthing>
parser = argparse.ArgumentParser()
parser.add_argument(...)
# I'd like the following to be true:
parser.parse_args("--foo A".split()).foo == CONST_A
parser.parse_args("--foo B".split()).foo == CONST_B
我可以用什么代替 ...
?
我能用 const
做的最好的事情是:
import argparse
# Both are the same type
CONST_A = 10
CONST_B = 20
parser = argparse.ArgumentParser()
status_group = parser.add_mutually_exclusive_group(required=True)
status_group.add_argument("-a", const=CONST_A, action='store_const')
status_group.add_argument("-b", const=CONST_B, action='store_const')
# I'd like the following to be true:
print parser.parse_args("-a".split()).a == CONST_A # True
print parser.parse_args("-b".split()).b == CONST_B # True
请注意,常量保存在两个不同的属性 a
和 b
中, 不适合我:(
这是一个有趣的问题。据我所知,argparse
不直接支持此功能。
如果您发现这种模式经常出现,您可以编写一个小实用程序 class 来为您完成此操作,transforming args
into a dictionary via vars
:
class Switcher(object):
def __init__(self, d):
self._d = d
def __call__(self, args):
args_ = vars(args)
for k, v in self._d.items():
if args_[k]:
return v
您可以按如下方式使用。假设您的解析器定义为:
import argparse
parser = argparse.ArgumentParser()
g = parser.add_mutually_exclusive_group()
g.add_argument('-a', action='store_true', default=False)
g.add_argument('-b', action='store_true', default=False)
然后你可以定义一个Switcher
通过:
s = Switcher({'a': 10, 'b': 20})
并像这样使用它:
>>> print s(parser.parse_args(['-a']))
10
>>> print s(parser.parse_args(['-b']))
20
最简单的方法是利用 add_argument
中的 type=
选项,就像
def argconv(**convs):
def parse_argument(arg):
if arg in convs:
return convs[arg]
else:
msg = "invalid choice: {!r} (choose from {})"
choices = ", ".join(sorted(repr(choice) for choice in convs.keys()))
raise argparse.ArgumentTypeError(msg.format(arg,choices))
return parse_argument
然后用 type=argconv(A=CONST_A, B=CONST_B)
:
...
parser.add_argument("--foo", type=argconv(A=CONST_A, B=CONST_B))
然后在您的示例中一切都会按照您希望的方式运行。
以下是我发布的第一个答案,它仍然有效,但不像上面的解决方案那么简单。
另一种方法是创建一个继承自 argparse.ArgumentParser
的 class 并覆盖 parse_args
以修改生成的结果:
import argparse
class MappedParser(argparse.ArgumentParser):
mapping = {} #backup if you don't use def_mapping
def def_mapping(self,**options):
self.mapping = options
def parse_args(self,args=None,namespace=None):
result = argparse.ArgumentParser.parse_args(self,args,namespace)
for name,options in self.mapping.items(): #by default this is is empty so the loop is skipped
if name in result:
key = getattr(result,name)
if key in options:
replace_with = options[key]
setattr(result,name,replace_with)
else:
self.error("option {name!r} got invalid value: {key!r}\n must be one of {valid}".format(name=name,key=key,valid=tuple(options.keys())))
return #error should exit program but I'll leave this just to be safe.
return result
这样,您的(示例)程序的其余部分将如下所示:
# There is nothing restricting their type.
CONST_A = "<something>"
CONST_B = ["other value", "type is irrelevent"]
parser = MappedParser() #constructor is same
parser.def_mapping(foo={"A":CONST_A, "B":CONST_B})
parser.add_argument("--foo") # and this is unchanged
# the following is now true:
print(parser.parse_args("--foo A".split()).foo is CONST_A)
print(parser.parse_args("--foo B".split()).foo is CONST_B)
#note that 'is' operator works so it is even the same reference
#this gives decent error message
parser.parse_args("--foo INVALID".split())
print("when parser.error() is called the program ends so this never is printed")
像这样添加额外的选项:
parser.def_mapping(foo={"A":CONST_A, "B":CONST_B,"C":"third option"})
或像这样的额外参数:
parser.def_mapping(foo={"A":CONST_A, "B":CONST_B},
conv={"int":int,"float":float})
以及任何未在 def_mapping
中指定的添加参数都被保留,因此很容易实现。
我不会创建新的动作或对象:只需使用 dict
来存储常量,用 choices
限制 --foo
的值并使用 parsed_args.foo
索引 dict
:
import argparse
CONST = {'A': 'something',
'B': 'otherthing'}
parser = argparse.ArgumentParser()
parser.add_argument('--foo', choices=('A', 'B'))
assert CONST[parser.parse_args("--foo A".split()).foo] == 'something'
assert CONST[parser.parse_args("--foo B".split()).foo] == 'otherthing'
choices
和 dict
键的组合基本上修复了 foo
的结果。
如果你想让事情变得更清晰或更简单,你可以在解析参数后将
foo
重新分配给你的 dict
值:
args = parser.parse_args("--foo A".split())
args.foo = CONST[args.foo]
现在 args.foo
直接等于 'something'
。
这对于自定义 type
参数来说是一个很好的例子:
CONST_A='<A>'
CONST_B='<B>'
def footype(astring):
dd = {'A':CONST_A, 'B':CONST_B}
try:
return dd[astring]
except KeyError:
raise argparse.ArgumentTypeError('enter A or B')
parser = argparse.ArgumentParser()
parser.add_argument('--foo', type=footype)
它会产生一个类似
的命名空间Namespace(foo='<A>')
和错误消息(如果给定 --foo C
)如:
usage: stack35648071.py [-h] [--foo FOO]
stack35648071.py: error: argument --foo: enter A or B
我尝试添加 choices
但帮助消息不正确。使用 metavar
和 help
来指导您的用户。
根据 docs:
The action keyword argument specifies how the command-line arguments should be handled. You may also specify an arbitrary action by passing an Action subclass or other object that implements the same interface.
In [5]: import argparse
...:
...: class MappingAction(argparse.Action):
...: def __init__(self, option_strings, dest, mapping, **kwargs):
...: self.mapping = mapping
...: super(MappingAction, self).__init__(option_strings, dest, **kwargs)
...:
...: def __call__(self, parser, namespace, values, option_string=None):
...: values = self.mapping.get(values, None)
...: setattr(namespace, self.dest, values)
...:
In [6]: parser = argparse.ArgumentParser()
...: mapping = {
...: 'A': '<something>',
...: 'B': '<otherthing>',
...: }
...: parser.add_argument('bar', action=MappingAction, choices=mapping.keys(), mapping=mapping)
In [7]: args = parser.parse_args('A'.split())
In [8]: args.bar
Out[8]: '<something>'