Allow list.append to take *args

I often run into situations where conditions necessitate the addition of multiple elements.

For example, when interfacing with an external CLI, you are guaranteed to see code like https://github.com/DataDog/integrations-core/blob/afba1b6e8496853b39a77e6e2e9987af2e067e5c/datadog_checks_dev/datadog_checks/dev/tooling/e2e/docker.py#L239-L267

Both current options are sub-optimal: you either must call .append twice or create a temporary sequence to .extend.

This should also apply to deque.[append|appendleft].

If we were to change the C implementation for list.append() to take any number of arguments, I believe it would result in a significant performance loss for single arg appends. This is very significant, considering how frequently it’s used; particularly in decent-sized for loops with many calls to list.append().

In the case of the example posted above, I don’t see it as an issue to have to call append twice. If you’re adding many items (4+), you can simply create a temporary list or any other iterable and use list.extend(). The performance and memory cost of doing so is very negligible, especially considering that the need comes up rather infrequently in comparison to using a single list.append().

Also, multi-arg append was intentionally made illegal, back in Python 1.6. See https://grokbase.com/t/python/python-list/002wztgmmg/multi-argument-append-is-illegal. The context was a bit different (apparently, it previously would append a tuple), but I think some of the same points in there may still be relevant.

As a result, I personally don’t think we should change list.append() to take multiple arguments. This seems to be the case of a feature that sounds convenient in theory, but in practice would be too much of a detriment to be worth changing the existing implementation.

2 Likes

Agreed. list.extend() is meant to add multiple elements to a list.
list.append() is meant for adding a single element.

We don’t need two ways to do the same.

Actually we have two ways to do the same: extend() and +=.

We don’t need yet one way more to do the same.

1 Like

Okay, sounds like a no-go :slightly_smiling_face:

Question though:

Why would that be?

[Serhiy]
“Actually we have two ways to do the same: extend() and +=.
We don’t need yet one way more to do the same.”

Too late!

> L = [1, 2, 3]
> L[len(L):] = [998, 999]
> L
[1, 2, 3, 998, 999]

wink

[Ofek]
“Both current options are sub-optimal: you either must call .append
twice or create a temporary sequence to .extend.”

If the signature of list.append became:

def append(self, *args)

then every time you call append, even with a single argument, the
interpreter must create a temporary sequence (a tuple) for args.

2 Likes

Ah that makes sense, thanks!

This is why I did not write “three ways”. There are many ways to do this, but extend() and += are two most obvious, simple and efficient.

In L[len(L):] = you need to repeat the reference to the list twice. But you can use L[sys.maxsize:] = with the same effect.

1 Like

Actually, the recent versions of CPython (3.6+) can avoid creating a temporary tuple (using the private METH_FASTCALL calling convention). But the code for a single parameter will likely be a tiny bit more efficient.

2 Likes

You should not worry about temporary sequence objects. Python is efficient for that, especially if you use a tuple instead of a list.

1 Like