使用 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个回答

2
你可以制作一个 自定义参数类型,并使用 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>]]

您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Brosef
我有点困惑。我原以为type指的是实际参数类型,比如float、int等。但现在看来它似乎指的是接受这些参数的函数。如果我更改def packages()的名称,那么type=packages就会出错... - Brosef
我尝试添加初始化器来初始化类,但是当我到达packages时,出现了“无效的packages格式”错误。python test.py --initializer="1" --packages ="[(1, 1.00), (2, 2.00)]" - Brosef
@Brosef,是的,因为预期格式应该是:一个列表,其中每个项目都是包含3个项的元组。 - alecxe
抱歉,我将其中一个项目注释掉了,所以现在列表只有两个项目。此外,当我将它们组合在一起时,初始化程序似乎无法正常工作。 - Brosef
显示剩余11条评论

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__。例如,您可以这样做。
parser.error('--package requires an int float and int')

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

说实话,我更喜欢你的答案而不是我的 :) - alecxe

1
这是我的提名; 它使用简单的解析器,将自定义内容放在Package类中。
它可以像这样调用:
python prog.py -p 0 1 2 --package 2 3 4

其中-p--package后面跟随3个值,并且可以重复使用(action为'append')。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__ 方法产生了一个漂亮的列表显示。

使用非数字的标签运行的示例:

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函数在这里无法正常工作。它将分别应用于这三个字符串,而不是作为一个整体。
自定义Action类可以处理这三个值,将每个值转换为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]非常简单,我认为没有必要定制解析器或其操作。


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