简体   繁体   中英

python + argparse - how to get order of optional arguments from command line

I would like to know how to get order of optional argument passed from commandline to argparse

I have image processing class which is able to apply different actions to image - like rotate, crop, resize...

And order in which these actions are applied is often essential (for example: you want to crop image before you resize it)

I have this code:

parser = argparse.ArgumentParser(description='Image processing arguments')

parser.add_argument('source_file', help='source file')
parser.add_argument('target_file', help='target file')

parser.add_argument('-resize', nargs=2, help='resize image', metavar=('WIDTH', 'HEIGHT'))
parser.add_argument('-rotate', nargs=1, help='rotate image', metavar='ANGLE')
parser.add_argument('-crop', nargs=4, help='crop image', metavar=('START_X','START_Y','WIDTH','HEIGHT'))

ar = parser.parse_args()

print ar

But - no matter in which order I pass parameters to script:

cmd.py test.jpg test2.jpg -crop 10 10 200 200 -resize 450 300

cmd.py test.jpg test2.jpg -resize 450 300 -crop 10 10 200 200

in Namespace items are always in same order (alphabetical I suppose):

Namespace(crop=['10', '10', '200', '200'], resize=['450', '300'], rotate=None, source_file='test.jpg', target_file='test
2.jpg')

Is there way to order them by position in command line string or to get their index?

You could always peek at sys.argv which is a list (and thus ordered) and simply iterate over it checking which argument comes first or use the list.index() to see the respective positions of your keywords in the list...

sys.argv contains a list of the words entered in the command line (the delimiter of such "word" is a space unless a string was surrounded by quotation marks). This means that if the user entered something like ./my_proggie -resize 500 then sys.argv would contain a list like this: ['./my_proggie', '-resize', '500'] .

The Namespace is a simple object whose str() lists its attributes according to the order of the keys in its __dict__ . Attributes are set with setattr(namespace, dest, value) .

One solution is to define a custom Namespace class. For example:

class OrderNamespace(argparse.Namespace):
    def __init__(self, **kwargs):
        self.__dict__['order'] = []
        super(OrderNamespace, self).__init__(**kwargs)
    def __setattr__(self,attr,value):
        self.__dict__['order'].append(attr)
        super(OrderNamespace, self).__setattr__(attr, value)

and use

args = parser.parse_args(None, OrderNamespace())

producing for your two examples

OrderNamespace(crop=..., order=[..., 'crop', 'resize'], resize=...)
OrderNamespace(crop=..., order=[..., 'resize', 'crop'], resize=...)

The order attribute gives the order in which the other attributes are set. The initial items are for defaults and the file positionals. Adding default=argparse.SUPPRESS to the arguments will suppress some of these items. This custom class could be more elaborate, using for example an OrderedDictionary, only noting the order for selected arguments, or using order to control the display of the attributes.

Another option is to use a custom Action class that creates this order attribute, eg

class OrderAction(argparse._StoreAction):
    def __call__(self, parser, namespace, values, option_string=None):
        setattr(namespace, self.dest, values)
        order = getattr(namespace, 'order') if hasattr(namespace, 'order') else []
        order.append(self.dest)
        setattr(namespace, 'order', order)

I adapted the approach from hpaulj:

    class OrderNamespace(argparse.Namespace):
        def __init__(self, **kwargs):
            self.__dict__['order'] = []
            super(OrderNamespace, self).__init__(**kwargs)
        def __setattr__(self,attr,value):
            if value:
              self.__dict__['order'].append(attr)
            super(OrderNamespace, self).__setattr__(attr, value)


    parser.add_argument('-g',action='append',default=argparse.SUPPRESS,help='some action')

By adding the "if value:" ... you only get every used argument the correct number of times.

There is a problem with @Martin 's solution: it does not work with cases like this:

parser.add_argument('-s', '--slong', action='store_false')

Here is my solution:

import argparse

class OrderedNamespace(argparse.Namespace):
    def __init__(self, **kwargs):
        self.__dict__["_order"] = []
        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):
        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)

Output is:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM