2

In Python I can extend a list in place like such

mylist = [1,2]
mylist.extend([3])
#or
mylist += [3]

and I can extend a list 'out of place', where it creates a copy, or third list

newList = mylist + [3]
mylist = mylist + [3] #assigns mylist a newly created list

I can append to a list in place:

mylist.append(3)

Is there a clean way to append to a list 'out of place' while avoiding the overhead of wrapping a single object in a list and without using the Copy module?

newList = mylist.append(3) #newList is None here
John Kugelman
  • 349,597
  • 67
  • 533
  • 578
  • 4
    `newList = mylist + [3]` like you already did. There’s also the new syntax `[*mylist, 3]`, but for a list that’s not significantly different. – Ry- Apr 22 '23 at 00:20
  • 1
    How would that be different from extending out of place? – Daniel Walker Apr 22 '23 at 00:20
  • 1
    @DanielWalker with extend you have to wrap the values in a list [], even if its a single object. – Andrés Estrella Apr 22 '23 at 00:26
  • Ah, I see. So, you'd like to avoid the overhead of creating a singleton list? – Daniel Walker Apr 22 '23 at 00:30
  • @DanielWalker, yeah even though it is a miniscule overhead, it would also make the code more straight forward (imo) – Andrés Estrella Apr 22 '23 at 00:42
  • 2
    Are you aware of `list.copy()`? or `list[:]` which does the same thing in a more cryptic way? Then you could do `newList = mylist.copy(); mylist.append(3)`. – wjandrea Apr 22 '23 at 00:50
  • 3
    If you're really going to try to microoptimize that hard, remember to check your assumptions about what's expensive. For example, you mentioned the `copy` module. `copy.copy` is *far* more expensive than any operation you tried in your question - with `mylist = [1, 2, 3]`, `copy.copy(mylist)` takes about 3 times as long as `mylist + [4]` in my tests, due in large part to overhead involved in dispatching to the right copy implementation. The overhead of creating a 1-element list object is much smaller. – user2357112 Apr 22 '23 at 00:52
  • @wjandrea These seem to be the way, and I agree they are more cryptic. I will compare the performance on these vs extend – Andrés Estrella Apr 22 '23 at 00:55
  • "avoiding the overhead of wrapping a single object in a list" that overhead is essentially negligible. Want to make it even more negligible, use a singleton tuple, although, I think the compiler actually optimizes it to that anyway, as pointed out in the answer below, the idiomatic way in modern python is to use `[*mylist, x]` but the consideration really isn't performance – juanpa.arrivillaga Apr 22 '23 at 01:43

2 Answers2

5

To avoid creating a new single-element list, you can use Python 3.5’s unpacking in list expressions, which expands an arbitrary iterable into the list:

newList = [*mylist, 3]

This is a micro-optimization at best. For example:

$ python -m timeit -s 'a = list(range(1000))' 'a + [4]'
50000 loops, best of 5: 4.12 usec per loop
$ python -m timeit -s 'a = list(range(1000))' '[*a, 4]'
50000 loops, best of 5: 4.03 usec per loop

You should optimize for readability.

There’s no built-in function that works like append but returns a new list.

Ry-
  • 218,210
  • 55
  • 464
  • 476
  • `dis.dis('[*a, 4]')` shows me a `LIST_APPEND` for the `4`, so you could kinda argue that that ***is*** what your last paragraph denies. – Kelly Bundy Apr 22 '23 at 01:46
  • @KellyBundy: Well, a bytecode instruction. Context for the last paragraph is the comments: the OP wanted something `append`-like for readability, not that I agree that it would actually be better… – Ry- Apr 22 '23 at 01:47
  • To me, their comments look like they just don't like the overhead/look of the singleton list. The list display does avoid that. – Kelly Bundy Apr 22 '23 at 01:53
2

You don't need to use the copy module; lists can copy themselves:

>>> mylist = [1, 2]
>>> newList = mylist.copy()
>>> newList.append(3)
>>> newList
[1, 2, 3]
>>> mylist
[1, 2]

I think it's important to note that "out of place" isn't really a thing, in that list.extend and list.__add__ are fundamentally different operations. The difference is that list.extend takes any iterable, for example:

>>> a = [1, 2]
>>> a.extend(range(3))
>>> a
[1, 2, 0, 1, 2]
>>> a + range(5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate list (not "range") to list

So the way to get access to the mutating methods on a new list, including .extend, is to make a copy.

wjandrea
  • 28,235
  • 9
  • 60
  • 81