Understanding *x ,= lst

PythonPython 3.xIterable Unpacking

Python Problem Overview


I'm going through some old code trying to understand what it does, and I came across this odd statement:

*x ,= p

p is a list in this context. I've been trying to figure out what this statement does. As far as I can tell, it just sets x to the value of p. For example:

p = [1,2]
*x ,= p    
print(x)

Just gives

[1, 2]

So is this any different than x = p? Any idea what this syntax is doing?

Python Solutions


Solution 1 - Python

*x ,= p is basically an obfuscated version of x = list(p) using extended iterable unpacking. The comma after x is required to make the assignment target a tuple (it could also be a list though).

*x, = p is different from x = p because the former creates a copy of p (i.e. a new list) while the latter creates a reference to the original list. To illustrate:

>>> p = [1, 2]
>>> *x, = p 
>>> x == p
True
>>> x is p
False
>>> x = p
>>> x == p
True
>>> x is p
True

Solution 2 - Python

It's a feature that was introduced in Python 3.0 (PEP 3132). In Python 2, you could do something like this:

>>> p = [1, 2, 3]
>>> q, r, s = p
>>> q
1
>>> r
2
>>> s
3

Python 3 extended this so that one variable could hold multiple values:

>>> p = [1, 2, 3]
>>> q, *r = p
>>> q
1
>>> r
[2, 3]

This, therefore, is what is being used here. Instead of two variables to hold three values, however, it is just one variable that takes each value in the list. This is different from x = p because x = p just means that x is another name for p. In this case, however, it is a new list that just happens to have the same values in it. (You may be interested in https://stackoverflow.com/q/1132941/5827958)

Two other common ways of producing this effect are:

>>> x = list(p)

and

>>> x = p[:]

Since Python 3.3, the list object actually has a method intended for copying:

x = p.copy()

The slice is actually a very similar concept. As nneonneo pointed out, however, that works only with objects such as lists and tuples that support slices. The method you mention, however, works with any iterable: dictionaries, sets, generators, etc.

Solution 3 - Python

You should always throw these to dis and see what it throws back at you; you'll see how *x, = p is actually different from x = p:

dis('*x, = p')
  1           0 LOAD_NAME                0 (p)
              2 UNPACK_EX                0
              4 STORE_NAME               1 (x)

While, the simple assignment statement:

dis('x = p')
  1           0 LOAD_NAME                0 (p)
              2 STORE_NAME               1 (x)

(Stripping off unrelated None returns)

As you can see UNPACK_EX is the different op-code between these; it's documented as:

> Implements assignment with a starred target: Unpacks an iterable in TOS (top of stack) into individual values, where the total number of values can be smaller than the number of items in the iterable: one of the new values will be a list of all leftover items.

Which is why, as Eugene noted, you get a new object that's referred to by the name x and not a reference to an already existing object (as is the case with x = p).


*x, does seem very odd (the extra comma there and all) but it is required here. The left hand side must either be a tuple or a list and, due to the quirkiness of creating a single element tuple in Python, you need to use a trailing ,:

i = 1, # one element tuple

If you like confusing people, you can always use the list version of this:

[*x] = p

which does exactly the same thing but doesn't have that extra comma hanging around there.

Solution 4 - Python

You can clearly understand it from below example

L = [1, 2, 3, 4]
while L:
    temp, *L = L             
    print(temp, L)

what it does is, the front variable will get the first item every time and the remaining list will be given to L.

The output will look shown below.

1 [2, 3, 4]
2 [3, 4]
3 [4]
4 []

Also look at below example

x, *y, z = "python"
print(x,y,z)

In this both x,z will get each one letter from the string meaning first letter is assigned to x and the last letter will be assigned to z and the remaining string will be assigned to variable y.

p ['y', 't', 'h', 'o'] n

One more example,

a, b, *c = [0,1,2,3]
print(a,b,c)

0 1 [2,3]

Boundary case: If there is nothing remaining for star variable then it will get an empty list.

Example:

a,b=[1]
print(a,b)

1 []

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKewlView Question on Stackoverflow
Solution 1 - PythonEugene YarmashView Answer on Stackoverflow
Solution 2 - PythonzondoView Answer on Stackoverflow
Solution 3 - PythonDimitris Fasarakis HilliardView Answer on Stackoverflow
Solution 4 - PythonRaviView Answer on Stackoverflow