Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Please explain reasoning behind code snippet from argparse module

I was looking at the source code for the built-in argparse._AppendAction, which implements the "append" action, and puzzled over the way it is implemented:

    def __call__(self, parser, namespace, values, option_string=None):
        items = _copy.copy(_ensure_value(namespace, self.dest, []))
        items.append(values)
        setattr(namespace, self.dest, items)

To break it down:

  • _ensure_value is like dict.setdefault for attributes. That is, if namespace has an attribute with the name self.dest then it is returned, if not it is set to [] and returned.
  • _copy.copy(x) returns just a shallow copy. When x is a list it is exactly like list(x) (but slower).
  • Then the item is appended to the copy of the list gotten from namespace.
  • Finally the self.dest attribute of namespace is overwritten with the copy, which should cause the old list to be garbage collected.

Why do it in such a roundabout and inefficient way, throwing away a whole list for each append? Why doesn't this suffice?

    def __call__(self, parser, namespace, values, option_string=None):
        items = _ensure_value(namespace, self.dest, [])
        items.append(values)
like image 372
Lauritz V. Thaulow Avatar asked Jan 15 '13 13:01

Lauritz V. Thaulow


1 Answers

I'm not an expert in the implementation, so (disclaimer) this is really just a guess. With this implementation, the user can pass a list as a default=... in a call to add_argument without it being mutated within argparse. Perhaps this type of safety was desired by the developers for one reason or another.

The inefficiency you mention really isn't a big deal. It's for parsing commandline arguments, so this function is likely only called 10's of times per program under heavy usage.


I've tested this and indeed, If I use the following script (where argparse_temp is simply argparse.py copied to the current directory so I can play with it):

import argparse_temp as argparse

lst = [1,2,3]
parser = argparse.ArgumentParser()
parser.add_argument('-l',default=lst,action='append')
print parser.parse_args()
print lst

This prints (when called as: python test1.py -l 4):

Namespace(l=[1, 2, 3, '4'])
[1, 2, 3]

with argparse as is, but:

Namespace(l=[1, 2, 3, '4'])
[1, 2, 3, '4']

with your proposed change.

If you print the action that is returned by add_argument, you get:

_AppendAction(option_strings=['-l'], dest='l', nargs=None, const=None, default=[1, 2, 3, '4'], type=None, choices=None, help=None, metavar=None)

Which is it conceivable that argparse depends on that action elsewhere in the implementation. (Notice that default has been mutated here as well).

like image 120
mgilson Avatar answered Nov 15 '22 14:11

mgilson