使用argparse解析对象列表

时间:2016-01-16 02:58:46

标签: python terminal argparse

我有一个带有函数的程序,它接受类初始化器和对象列表。每个对象由3个变量id,value和tag组成。

class Package():

    def __init__(self, id, value, name):
        if (value <= 0):
            raise ValueError("Amount must be greater than 0")
        self.id = id
        self.value = value
        self.tag = tag


 class Purchase():

    def submit(some_list):
        //Do stuff

def main():
    //Help here!
    parser = argparse.ArgumentParser()
    parser.add_argument("id", help="ID")
    parser.add_argument("value", help="Value")
    parser.add_argument("tag", help="Tag")
    args = parser.parse_args()
    some_list = [args.id, args.value, args.tag]
    submit(some_list)

我正在尝试在main()中实现argparse,所以我可以通过执行以下操作来运行程序:python foo.py "int0 [(int1, float1, int2), (int3, float2, int4) ....]"。列表中的对象数是可变的,取决于用户输入。

initializer = num0

//First package object
package.id = num1
package.value = num2
package.tag = num3

//Second package object
package.id = num4
package.value = num5
package.tag = num6  

3 个答案:

答案 0 :(得分:2)

您可以制作自定义argument type 并使用ast.literal_eval()来解析该值。

工作样本:

import argparse
from ast import literal_eval


class Package():
    def __init__(self, id, value, tag):
        if (value <= 0):
            raise ValueError("Amount must be greater than 0")
        self.id = id
        self.value = value
        self.tag = tag


def packages(s):
    try:
        data = literal_eval(s)
    except:  # TODO: avoid bare except and handle more specific errors
        raise argparse.ArgumentTypeError("Invalid 'packages' format.")

    return [Package(*item) for item in data]


parser = argparse.ArgumentParser()
parser.add_argument('--packages', dest="packages", type=packages, nargs=1)
args = parser.parse_args()
print(args.packages)

现在,如果您要运行该脚本,您将获得打印的Package类实例列表:

$ python test.py --packages="[(1, 1.02, 3), (40, 2.32, 11)]"
[[<__main__.Package instance at 0x10a20d368>, <__main__.Package instance at 0x10a20d4d0>]]

答案 1 :(得分:2)

我希望更加明确并使用自定义操作:

import argparse

class PackageAction(argparse.Action):
    def __init__(self, *args, **kwargs):
        super(PackageAction, self).__init__(*args, **kwargs)
        self.nargs = 3

    def __call__(self, parser, namespace, values, option_string):
        lst = getattr(namespace, self.dest, []) or []
        a, b, c = values
        lst.append(Package(int(a), float(b), int(c)))
        setattr(namespace, self.dest, lst)

class Package(object):
    def __init__(self, foo, bar, baz):
        self.foo = foo
        self.bar = bar
        self.baz = baz

    def __repr__(self):
        return 'Package(%r, %r, %r)' % (self.foo, self.bar, self.baz)

parser = argparse.ArgumentParser()
parser.add_argument('--package', action=PackageAction)

print(parser.parse_args())

这里的用法类似于:

$ python packager.py --package 1 2 3 --package 4 5 6
Namespace(package=[Package(1, 2.0, 3), Package(4, 5.0, 6)])

一个好处是你可以获得更好的默认错误处理......例如:

$ python ~/sandbox/test.py --package 1 2 3 --package 4 5
usage: test.py [-h] [--package PACKAGE PACKAGE PACKAGE]
test.py: error: argument --package: expected 3 argument(s)

当然,您可以根据自己的目的进行修改 - 特别是为__call__提供额外的error handling可能会更好。例如你可以做点什么

parser.error('--package requires an int float and int')

如果用户传递了错误的字符串。您还可以提供更好的变量名称: - )

答案 2 :(得分:1)

这是我的提名;它使用普通的解析器,并将自定义放在Package类中。

它会被称为:

python prog.py -p 0 1 2 --package 2 3 4

其中-p--package后跟3个值,可能会重复(action是'追加')。 nargs=3确保每个-p后跟3个值(否则解析器会引发错误)。将这些值转换为数字(并引发错误)是Package类的责任。该课程已经检查了非负面value

import argparse
class Package():
    def __init__(self, id, value, tag):
        # 3 inputs - numbers, but equivalent strings are accepted
        # may add more value validation
        self.id = int(id)
        self.value = float(value)
        if self.value <= 0:
            raise ValueError("Amount must be greater than 0")
        self.tag = int(tag)
    def __repr__(self):
        return 'Package (%s, %s, %s)'%(self.id, self.value, self.tag)

def main(argv):
    parser = argparse.ArgumentParser()
    parser.add_argument('-p', '--package', nargs=3, action='append', default=[],
        metavar=('ID','Value','tag'), help='package parameters; may repeat')
    args = parser.parse_args(argv)
    print args
    packages = [Package(*v) for v in args.package]
    return packages
    # alt
    # args.package = packages; return args

if __name__ == '__main__':
    import sys
    if sys.argv[1:]:
        print main(sys.argv[1:])
    else:
        # test cases
        print main([]) # nothing
        print main('-p 1 2 3'.split())
        print main('-p 0 1 2 --pack 2 3 4'.split())
        print main(['-h']) # help

测试用例的示例运行是:

2030:~/mypy$ python stack34823075.py 
Namespace(package=[])
[]
Namespace(package=[['1', '2', '3']])
[Package (1, 2.0, 3)]
Namespace(package=[['0', '1', '2'], ['2', '3', '4']])
[Package (0, 1.0, 2), Package (2, 3.0, 4)]

usage: stack34823075.py [-h] [-p ID Value tag]

optional arguments:
  -h, --help            show this help message and exit
  -p ID Value tag, --package ID Value tag
                        package parameters; may repeat

请注意metavar如何影响帮助显示。 Package __repr__方法会生成一个很好的列表显示。

使用非数字tag运行的示例:

2038:~/mypy$ python stack34823075.py -p 1 2.3 tag
Namespace(package=[['1', '2.3', 'tag']])
Traceback (most recent call last):
  File "stack34823075.py", line 31, in <module>
    print main(sys.argv[1:])
  File "stack34823075.py", line 20, in main
    packages = [Package(*v) for v in args.package if v is not None]
  File "stack34823075.py", line 10, in __init__
    self.tag = int(tag)
ValueError: invalid literal for int() with base 10: 'tag'

特殊的type功能在这里效果不佳。它将分别应用于3个字符串中的每个字符串,而不是作为一个组。

自定义Action类可以处理3个值,将每个值转换为intfloatint。但即使在那里,我也希望将它们传递给Package,例如

def __call__(self, namespace, dest, values):
    # store_action style
    new_value = Package(*values)
    setattr(namespace, dest, new_value) # store action

但由于packages = [Package(*v) for v in args.package]非常简单,我在定制解析器或其操作方面没有太多意义。