10

Is there any practical difference between list(iterable) and [*iterable] in versions of Python that support the latter?

Chris_Rands
  • 38,994
  • 14
  • 83
  • 119
Roman Odaisky
  • 2,811
  • 22
  • 26
  • 7
    Corner case: it's possible to rebind the name `list` to something other than the built-in type, but you can't change the meaning of the `[*iterable]` syntax. – chepner Sep 27 '18 at 14:39

5 Answers5

7

list(x) is a function, [*x] is an expression. You can reassign list, and make it do something else (but you shouldn't).

Talking about cPython, b = list(a) translates to this sequence of bytecodes:

LOAD_NAME                1 (list)
LOAD_NAME                0 (a)
CALL_FUNCTION            1
STORE_NAME               2 (b)

Instead, c = [*a] becomes:

LOAD_NAME                0 (a)
BUILD_LIST_UNPACK        1
STORE_NAME               3 (c)

so you can argue that [*a] might be slightly more efficient, but marginally so.

Stefano Sanfilippo
  • 32,265
  • 7
  • 79
  • 80
4

You can use the standard library module dis to investigate the byte code generated by a function. In this case:

import dis

def call_list(x):
    return list(x)

def unpacking(x):
    return [*x]

dis.dis(call_list)
#   2           0 LOAD_GLOBAL              0 (list)
#               2 LOAD_FAST                0 (x)
#               4 CALL_FUNCTION            1
#               6 RETURN_VALUE

dis.dis(unpacking)
#   2           0 LOAD_FAST                0 (x)
#               2 BUILD_LIST_UNPACK        1
#               4 RETURN_VALUE

So there is a difference and it is not only the loading of the globally defined name list, which does not need to happen with the unpacking. So it boils down to how the built-in list function is defined and what exactly BUILD_LIST_UNPACK does.

Note that both are actually a lot less code than writing a standard list comprehension for this:

def list_comp(x):
    return [a for a in x]

dis.dis(list_comp)
#   2           0 LOAD_CONST               1 (<code object <listcomp> at 0x7f65356198a0, file "<ipython-input-46-dd71fb182ec7>", line 2>)
#               2 LOAD_CONST               2 ('list_comp.<locals>.<listcomp>')
#               4 MAKE_FUNCTION            0
#               6 LOAD_FAST                0 (x)
#               8 GET_ITER
#              10 CALL_FUNCTION            1
#              12 RETURN_VALUE
Graipher
  • 6,891
  • 27
  • 47
1

Since [*iterable] is unpacking, it accepts assignment-like syntax, unlike list(iterable):

>>> [*[]] = []
>>> list([]) = []
  File "<stdin>", line 1
SyntaxError: can't assign to function call

You can read more about this here (not useful though).

You can also use list(sequence=iterable), i.e. with a key-word argument:

>>> list(sequence=[])
[]

Again not useful.

Chris_Rands
  • 38,994
  • 14
  • 83
  • 119
  • While one could write `[*var] = iterable` to mean `var = list(iterable)`, from readability standpoint it’s too much even to someone who’s no stranger to code golf. Also what an interesting symmetry between `[*var] = iterable` and `var = [*iterable]` (though broken in case of `(*var,) = iterable` and `var = (*iterable,)`). – Roman Odaisky Sep 27 '18 at 15:10
  • @RomanOdaisky Indeed, `*lst, = (1,2,2); print(lst)` being the simplest I guess, though still not for recommended use – Chris_Rands Sep 27 '18 at 15:27
  • Actually, I found a valid use case for this: `for keys, [*rows] in itertools.groupby(...):`! – Roman Odaisky Nov 28 '18 at 17:51
1

There's always going to be some differences between two constructs that do the same thing. Thing is, I wouldn't say the differences in this case are actually practical. Both are expressions that take the iterable, iterate through it and then create a list out of it.

The contract is the same: input is an iterable output is a list populated by the iterables elements.

Yes, list can be rebound to a different name; list(it) is a function call while [*it] is a list display; [*it] is faster with smaller iterables but generally performs the same with larger ones. Heck, one could even throw in the fact that [*it] is three less keystrokes.

Are these practical though? Would I think of them when trying to get a list out of an iterable? Well, maybe the keystrokes in order to stay under 79 characters and get the linter to shut it up.

Dimitris Fasarakis Hilliard
  • 150,925
  • 31
  • 268
  • 253
1

Apparently there’s a performance difference in CPython, where [*a] overallocates and list() doesn’t: What causes [*a] to overallocate?

Roman Odaisky
  • 2,811
  • 22
  • 26