Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use argparse to parse a list of objects

I have a program with a function that takes a class initializer and list of objects. Each object consists of 3 variables id, value, and 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)

I'm trying to implement argparse in main() so I can run the program by doing something like: python foo.py "int0 [(int1, float1, int2), (int3, float2, int4) ....]". The number of objects in the list is variable and depends on the user input.

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  
like image 651
Brosef Avatar asked Sep 17 '25 11:09

Brosef


2 Answers

You can make a custom argument type and use ast.literal_eval() to parse the value.

Working sample:

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)

Now if you would run the script, you would get a list of Package class instances printed:

$ python test.py --packages="[(1, 1.02, 3), (40, 2.32, 11)]"
[[<__main__.Package instance at 0x10a20d368>, <__main__.Package instance at 0x10a20d4d0>]]
like image 109
alecxe Avatar answered Sep 20 '25 01:09

alecxe


I would prefer to be a bit more explicit and use a custom action:

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())

The usage here would look something like:

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

One benefit is that you get slightly better default error handling ... e.g.:

$ 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)

Of course, you can modify to suit your purposes -- specifically it would probably be good to provide a little extra error handling to __call__. e.g. you could do something like

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

if the user passed bad strings. You could also provide better variable names :-)

like image 25
mgilson Avatar answered Sep 20 '25 02:09

mgilson