11

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)

1 Answer 1

6

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

Sign up to request clarification or add additional context in comments.

2 Comments

This sounds like the right answer. I guess it should be possible to avoid modifying the default argument while also improving efficiency, but I can't think of an elegant way of doing it, and as you say, this shouldn't be a bottleneck.
@lazyr -- And as a side note, copy.copy is used instead of slicing, probably so that the user isn't forced to pass a list. Anything with an append method would work.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.