argparse参数顺序

22

我有一个小问题。

我使用 argparse 来解析我的参数,它运行得非常好。

要获得这些参数,我做了以下操作:

p_args = parser.parse_args(argv)
args = dict(p_args._get_kwargs())

不过 p_args 的问题在于我不知道如何按照命令行中它们的位置获取这些参数,因为它是一个字典。

那么有没有可能按照命令行中它们的顺序将这些参数放入元组/列表/有序字典中呢?


5
请展示你设置解析器的代码,这可以帮助我们更好地理解。 - srgerg
6个回答

21
为了保持参数有序,我使用如下自定义操作:
import argparse
class CustomAction(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        if not 'ordered_args' in namespace:
            setattr(namespace, 'ordered_args', [])
        previous = namespace.ordered_args
        previous.append((self.dest, values))
        setattr(namespace, 'ordered_args', previous)
parser = argparse.ArgumentParser()
parser.add_argument('--test1', action=CustomAction)
parser.add_argument('--test2', action=CustomAction)

例如,要使用它:

>>> parser.parse_args(['--test2', '2', '--test1', '1'])
Namespace(ordered_args=[('test2', '2'), ('test1', '1')], test1=None, test2=None)

6
如果你需要知道解析器中参数的顺序,你可以像这样设置解析器:
import argparse

parser = argparse.ArgumentParser(description = "A cool application.")
parser.add_argument('--optional1')
parser.add_argument('positionals', nargs='+')
parser.add_argument('--optional2')

args = parser.parse_args()
print args.positionals

这是一个运行此代码的快速示例:
$ python s.py --optional1 X --optional2 Y 1 2 3 4 5
['1', '2', '3', '4', '5']

请注意,args.positionals是一个按顺序排列的位置参数列表。有关更多信息,请参阅argparse文档

2
我认为这种方法的一个后果是argparse不能/不会验证1,2,3,4,5(即强制用户仅输入optional2的合法值)。您必须在代码中运行optional2的值并验证它们。 - Michael Ray Lovett

6

这个方法有点脆弱,因为它依赖于对argparse.ArgumentParser的内部了解,但是如果不想重写argparse.ArgumentParser.parse_known_args的话,我会使用以下方法:

class OrderedNamespace(argparse.Namespace):
    def __init__(self, **kwargs):
        self.__dict__["_arg_order"] = []
        self.__dict__["_arg_order_first_time_through"] = True
        argparse.Namespace.__init__(self, **kwargs)

    def __setattr__(self, name, value):
        #print("Setting %s -> %s" % (name, value))
        self.__dict__[name] = value
        if name in self._arg_order and hasattr(self, "_arg_order_first_time_through"):
            self.__dict__["_arg_order"] = []
            delattr(self, "_arg_order_first_time_through")
        self.__dict__["_arg_order"].append(name)

    def _finalize(self):
        if hasattr(self, "_arg_order_first_time_through"):
            self.__dict__["_arg_order"] = []
            delattr(self, "_arg_order_first_time_through")

    def _latest_of(self, k1, k2):
        try:
            print self._arg_order
            if self._arg_order.index(k1) > self._arg_order.index(k2):
                return k1
        except ValueError:
            if k1 in self._arg_order:
                return k1
        return k2

这是通过了解 argparse.ArgumentParser.parse_known_args 会遍历整个选项列表一次,并为每个参数设置默认值来实现的。这意味着用户指定的参数从第一次__setattr__命中它之前的参数开始。

用法:

options, extra_args = parser.parse_known_args(sys.argv, namespace=OrderedNamespace())

你可以检查options._arg_order以查看用户指定的命令行参数的顺序,或者使用options._latest_of("arg1", "arg2")来查看在命令行上后面指定的是--arg1还是--arg2(这正是我需要的:看到两个选项中哪一个会被覆盖)。更新:必须添加_finalize方法来处理sys.argv()列表中不包含任何参数的病态情况。

2
只有在我对重构进行了一番折腾并运行了一堆测试之后,我才意识到在action='store_const'或action='store_true'等情况下,这基本上行不通。使用add_argument('-f', action='store_true')时,argparse不会将-f添加到参数顺序列表中,除非传递'const'或'default'参数给add_argument,否则它不会向命名空间中添加任何值,直到遇到实际参数。唉,这本来可以是一个优雅的解决方案。 - Silfheed

1
我需要这个功能是因为,出于日志记录的目的,我喜欢在解析后打印参数。问题是参数的顺序不是按照我的预期打印的,这真的很恼人。
自定义操作类对我来说根本不起作用。我有其他使用不同操作(如“store_true”)的参数,而且如果在命令行中没有给出参数,则默认参数也不起作用,因为自定义操作类不会被调用。对我有用的是创建一个类似于以下内容的包装类:
import collections

from argparse import ArgumentParser

class SortedArgumentParser():
    def __init__(self, *args, **kwargs):
        self.ap = ArgumentParser(*args, **kwargs)
        self.args_dict = collections.OrderedDict()      

    def add_argument(self, *args, **kwargs):
        self.ap.add_argument(*args, **kwargs)
        # Also store dest kwarg
        self.args_dict[kwargs['dest']] = None

    def parse_args(self):
        # Returns a sorted dictionary
        unsorted_dict = self.ap.parse_args().__dict__
        for unsorted_entry in unsorted_dict:
            self.args_dict[unsorted_entry] = unsorted_dict[unsorted_entry]

        return self.args_dict

优点是add_argument方法应该具有与原始的ArgumentParser完全相同的功能。缺点是,如果您想要其他方法,您将不得不为所有这些方法编写包装。幸运的是,我只使用了add_argumentparse_args,所以这对我来说非常好用。如果您想使用父ArgumentParser,则需要做更多的工作。

1
有一个专门处理这个的模块:

https://github.com/claylabs/ordered-keyword-args

不使用orderedkwargs模块

def multiple_kwarguments(first , **lotsofothers):
    print first

    for i,other in lotsofothers:
         print other
    return True

multiple_kwarguments("first", second="second", third="third" ,fourth="fourth" ,fifth="fifth")

输出:

first
second
fifth
fourth
third

关于使用orderedkwargs模块

from orderedkwargs import ordered kwargs  
@orderedkwargs  
def mutliple_kwarguments(first , *lotsofothers):
    print first

    for i, other in lotsofothers:
        print other
    return True


mutliple_kwarguments("first", second="second", third="third" ,fourth="fourth" ,fifth="fifth")

输出:

first
second
third
fourth
fifth

注意:在使用此模块与函数上方的装饰器时,需要一个单个星号。

我fork了它,以处理函数头中的kwargs为OrderedDict,并在函数体中使用与**kwargs相同的语法。请参见https://github.com/zellerede/Ordered-Keyword-Args/tree/OrderedDict。 - Berci
@Berci 很好,如果你愿意的话,也可以发起拉取请求。 - igauravsehrawat
1
这并没有回答问题,问题是关于使用argparse解析命令行参数,而不是关于函数参数的。 - Pont

0

这是我基于现有解决方案的简单解决方案:

class OrderedNamespace(argparse.Namespace):
    def __init__(self, **kwargs):
        self.__dict__["_order"] = [None]
        super().__init__(**kwargs)
    def __setattr__(self, attr, value):
        super().__setattr__(attr, value)
        if attr in self._order:
            self.__dict__["_order"].clear()
        self.__dict__["_order"].append(attr)
    def ordered(self):
        if self._order and self._order[0] is None:
            self._order.clear()
        return ((attr, getattr(self, attr)) for attr in self._order)

parser = argparse.ArgumentParser()
parser.add_argument('--test1', default=1)
parser.add_argument('--test2')
parser.add_argument('-s', '--slong', action='store_false')
parser.add_argument('--test3', default=3)

args = parser.parse_args(['--test2', '2', '--test1', '1', '-s'], namespace=OrderedNamespace())

print(args)
print(args.test1)
for a, v in args.ordered():
    print(a, v)

输出:

OrderedNamespace(_order=['test2', 'test1', 'slong'], slong=False, test1='1', test2='2', test3=3)
1
test2 2
test1 1
slong False

它允许在add_argument()中执行操作,这对于定制的操作类解决方案更加困难。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接