【问题标题】:argparse: map user input to defined constantargparse:将用户输入映射到定义的常量
【发布时间】:2016-06-09 10:52:50
【问题描述】:

我想构建一个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

请注意,常量被保存到两个不同的属性 ab,女巫不适合我:(

【问题讨论】:

  • 您是否执行了help(argparse) 或查看了文档? (docs.python.org/3/library/argparse.html) 我现在第一次研究它。
  • 我有,但是对于初学者来说有点毛骨悚然。顺便说一句,我正在编写 python2 代码。
  • add_argumentconstdefault 参数。试验一下。
  • @Evert 我可以用constaction="store_const" 做的最好的事情是将我的常量映射到返回的命名空间的两个不同属性中。

标签: python python-2.7 argparse


【解决方案1】:

最简单的方法是利用add_argument 中的type= 选项,就像@hpaulj 所做的那样,尽管它可以通过工厂函数进行泛化:

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 的类并覆盖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 中未指定的任何添加参数都将被单独保留,因此很容易实现。

【讨论】:

  • MappedParser 没问题,argconv 是金子。
【解决方案2】:

这是一个有趣的问题。据我所知,argparse 并不直接支持这一点。

如果您发现这种模式经常发生,您可以编写一个小型实用程序类来为您执行此操作,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

【讨论】:

  • 这是一个不错的选择。如果没有人挑战您并指出argparse 的一项确实 直接支持它的功能,您将获得绿色检查!
  • 很抱歉偷了你接受的答案标志,我希望argconv 的信用是足够的补偿:)
  • :-) 别担心。您的回答非常好 - 我自己赞成。
【解决方案3】:

这对于自定义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,但帮助信息不正确。使用metavarhelp 来指导您的用户。

【讨论】:

  • 非常感谢你发布这个,没有你我不会想到argconv
  • 这是迄今为止最好的策略,尽管它在使用嵌套函数时效果更好,然后在字典中硬编码。
【解决方案4】:

我不会创建新的操作或对象:只需使用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'

choicesdict 的键的组合基本上修复了foo 的结果。


如果您想让事情变得更清晰或更简单,您可以在解析参数后将foo 重新分配给您的dict 值:
args = parser.parse_args("--foo A".split())
args.foo = CONST[args.foo]

现在args.foo 直接等同于'something'

【讨论】:

  • 这就是我最终所做的。谢谢。
【解决方案5】:

根据docs

action 关键字参数指定应如何处理命令行参数。您还可以通过传递一个 Action 子类或其他实现相同接口的对象来指定任意操作。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-10-01
    • 1970-01-01
    • 2012-04-07
    • 1970-01-01
    • 1970-01-01
    • 2020-10-18
    • 1970-01-01
    相关资源
    最近更新 更多