Python assignment trickiness

Wednesday 10 January 2007

Here’s a well-known fact about Python assignment: When you have a list (or tuple) on the left-hand side, and a list (or tuple) on the right-hand side, each element on the left is assigned a value from the right, just as if it were in its own assignment statement:

>>> a, b, c = 1, 2, 3
>>> a, b, c
(1, 2, 3)
>>> [a, b, c] = (1, 2, 3)
>>> a
1
>>> b
2
>>> c
3

So far, I don’t think anyone is surprised by this. But notice the recursive nature of the definition. This means that an element on the left can itself be a list (or tuple):

>>> a, (b, c, d), e = 1, (2, 3, 4), 5
>>> c
3
>>> a, _, e = 1, (2, 3, 4), 5
>>> e
5

This can be helpful with functions returning multiple structured values:

def points():
    ''' Return two pairs of numbers. '''
    return (x1, y1), (x2, y2)

# Now I can call points and unpack it flexibly in the same statement:
a, b = points()
(ax, ay), b = points()
_, (bx, by) = points()
(ax, ay), (bx, by) = points()

Handy.

Comments

[gravatar]
Brandon Corfman 9:33 AM on 10 Jan 2007

I experimented once and found it works for function parameters too:
def print_point((x,y)):
   print x,y

pt = 1,2
print_point(pt)

But every time I start to use it, I think it may be too clever.

[gravatar]
Stuart Dootson 12:01 PM on 10 Jan 2007

So, Python has (some level of) pattern-matching - all the cool (IMO) languages (e.g. Haskell, OCaml, Erlang) do :-)

[gravatar]
Joe W. 1:07 PM on 10 Jan 2007

In "_, (bx, by) = points()", what does the underscore do?

[gravatar]
Ned Batchelder 1:48 PM on 10 Jan 2007

Brandon, thanks, I didn't realize the same trick applied to function parameters, though I think I agree with you that it is too tricky.

Joe: The underscore is not special. It's just another identifier, used in this case because it an unobstrusive name for a variable I'll never use. It's used just like "dummy", or some other name.

[gravatar]
DeanG 2:44 PM on 11 Jan 2007

Joe:
I'd be cautious of ever assigning a value to '_' when it's the interactive mode's object name for the last returned value.

>>> _
Traceback (most recent call last):
File "", line 1, in ?
NameError: name '_' is not defined
>>> print 5
5
>>> _
Traceback (most recent call last):
File "", line 1, in ?
NameError: name '_' is not defined
>>> 5
5
>>> _
5
>>>
===========
..and I'm glad to see Python uses a back-slash instead of an underscore to wrap an expression to a new line.

[gravatar]
Omer Trajman 3:33 PM on 11 Jan 2007

Great for use in list comprehension: [func(x) for (x,y) in points()]

[gravatar]
Patrick Maupin 12:58 PM on 17 Jan 2007

Actually, the right-hand side doesn't even need to be a tuple -- it just needs to follow the iterator protocol. This can be very handy.

Assume we have:

x = set([1,2])
y = set([2,3])

And we want to get the element that is the intersection.

You could do something like:

z = x & y
assert len(z) == 1
z = z.pop()

but instead, you can just do:

z, = x & y

This looks a little funny, but it throws an exception if there is not exactly one element in the intersection, and binds z to that element.

[gravatar]
Patrick Maupin 12:58 PM on 17 Jan 2007

Actually, the right-hand side doesn't even need to be a tuple -- it just needs to follow the iterator protocol. This can be very handy.

Assume we have:

x = set([1,2])
y = set([2,3])

And we want to get the element that is the intersection.

You could do something like:

z = x & y
assert len(z) == 1
z = z.pop()

but instead, you can just do:

z, = x & y

This looks a little funny, but it throws an exception if there is not exactly one element in the intersection, and binds z to that element.

Add a comment:

Ignore this:
Leave this empty:
Name is required. Either email or web are required. Email won't be displayed and I won't spam you. Your web site won't be indexed by search engines.
Don't put anything here:
Leave this empty:
URLs auto-link and some tags are allowed: <a><b><i><p><br><pre>.