问题
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?
回答1:
*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
回答2:
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 "Least Astonishment" and the Mutable Default Argument)
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.
回答3:
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.
来源:https://stackoverflow.com/questions/43190992/understanding-x-lst