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_valueis likedict.setdefaultfor attributes. That is, ifnamespacehas an attribute with the nameself.destthen it is returned, if not it is set to[]and returned._copy.copy(x)returns just a shallow copy. Whenxis a list it is exactly likelist(x)(but slower).- Then the item is appended to the copy of the list gotten from
namespace. - Finally the
self.destattribute ofnamespaceis 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)
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 toadd_argumentwithout 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_tempis simplyargparse.pycopied to the current directory so I can play with it):This prints (when called as:
python test1.py -l 4):with
argparseas is, but:with your proposed change.
If you print the action that is returned by
add_argument, you get:Which is it conceivable that argparse depends on that action elsewhere in the implementation. (Notice that
defaulthas been mutated here as well).