Python Assignment Operator Precedence - (a, b) = a[b] = {}, 5 - python

I saw this Python snippet on Twitter and was quite confused by the output:
>>> a, b = a[b] = {}, 5
>>> a
{5: ({...}, 5)}
What is going on here?

From the Assignment statements documentation:
An assignment statement evaluates the expression list (remember that this can be a single expression or a comma-separated list, the latter yielding a tuple) and assigns the single resulting object to each of the target lists, from left to right.
You have two assignment target lists; a, b, and a[b], the value {}, 5 is assigned to those two targets from left to right.
First the {}, 5 tuple is unpacked to a, b. You now have a = {} and b = 5. Note that {} is mutable.
Next you assign the same dictionary and integer to a[b], where a evaluates to the dictionary, and b evaluates to 5, so you are setting the key 5 in the dictionary to the tuple ({}, 5) creating a circular reference. The {...} thus refers to the same object that a is already referencing.
Because assignment takes place from left to right, you can break this down to:
a, b = {}, 5
a[b] = a, b
so a[b][0] is the same object as a:
>>> a, b = {}, 5
>>> a[b] = a, b
>>> a
{5: ({...}, 5)}
>>> a[b][0] is a
True

Related

`a = b = c` is not the same as `b = c` \n `a = b`? [duplicate]

I saw this Python snippet on Twitter and was quite confused by the output:
>>> a, b = a[b] = {}, 5
>>> a
{5: ({...}, 5)}
What is going on here?
From the Assignment statements documentation:
An assignment statement evaluates the expression list (remember that this can be a single expression or a comma-separated list, the latter yielding a tuple) and assigns the single resulting object to each of the target lists, from left to right.
You have two assignment target lists; a, b, and a[b], the value {}, 5 is assigned to those two targets from left to right.
First the {}, 5 tuple is unpacked to a, b. You now have a = {} and b = 5. Note that {} is mutable.
Next you assign the same dictionary and integer to a[b], where a evaluates to the dictionary, and b evaluates to 5, so you are setting the key 5 in the dictionary to the tuple ({}, 5) creating a circular reference. The {...} thus refers to the same object that a is already referencing.
Because assignment takes place from left to right, you can break this down to:
a, b = {}, 5
a[b] = a, b
so a[b][0] is the same object as a:
>>> a, b = {}, 5
>>> a[b] = a, b
>>> a
{5: ({...}, 5)}
>>> a[b][0] is a
True

Unpack into the list append method in Python

I run into a problem when unpacking a tuple. I want the first value to be appended to a list and a second assigned to a variable. For example:
list = []
tuple = (1, 2)
list.append, variable = tuple
But this raises an exception since I am assigning to a bultin and not actually calling in. Is that possible in Python? Or even a simpler operation such as:
a, b = 5, 4
tuple = (1, 2)
a+, b = tuple
to yield a = 6, b = 2.
There's no brief syntax to allow this. However, here's a class that creates a wrapper around a list, so that assigning to an append attribute really calls the underlying list's append method. This could be useful if you have a lot of values to append to the list.
class Appender:
def __init__(self, lst):
self.lst = lst
# The rare write-only property
append = property(None, lambda self, v: self.lst.append(v))
values = []
value_appender = Appender(values)
value_appender.append, b = (1,2)
assert values == [1]
Perhaps simpler, a subclass of list with a similar property:
class Appendable(list):
take = property(None, lambda self, v: self.append(v))
values = Appendable()
values.take, b = (1, 2)
assert values == [1]
append is a method on the builtin list type. Python allows tuple unpacking into variables in one line as a convenience, but it won't decide to call the append method with part of your tuple as an argument. Just write your code on multiple lines, that will help make it easier to read too.
my_list = []
my_tuple = (1, 2)
a, b = my_tuple
my_list.append(a)
Technically yes you could do it in a single line, but I wouldn't.
l = []
a = (1,2)
l[:0], b = [[x] if c == 0 else x for c,x in enumerate(a)]
>>> l
[1]
>>> b
2
You can use the map function on the append method for the list.
>>> a = (6,7)
>>> b = [1,2,3,4,5]
>>> list(map(b.append, a))
[None, None]
>>> b
[1, 2, 3, 4, 5, 6, 7]
I am not really sure what the list() does in this statement but it seems to work.

Python unpacking gotcha (unexpected behavior) [duplicate]

This question already has answers here:
Python Multiple Assignment Statements In One Line
(5 answers)
How does Python's comma operator work during assignment?
(3 answers)
Closed 3 years ago.
Can anybody explain what's going on here? Why does this happen?
>>> b = "1984"
>>> a = b, c = "AB"
>>> print(a, b, c)
'AB', 'A', 'B'
This behavior really blows my mind.
Found this here
Assignment is a statement; it's defined to assign the far right side to the various targets from left to right. The rather dry language grammar description is:
An assignment statement evaluates the expression list (remember that this can be a single expression or a comma-separated list, the latter yielding a tuple) and assigns the single resulting object to each of the target lists, from left to right.
So for example:
a = b = 1
assigns 1 to a, then assigns it again to b, roughly the same as if you did:
__specialtmp = 1
a = __specialtmp
b = __specialtmp
where __specialtmp is an unnamed temporary storage location (on CPython, it's just loaded on the top of the program stack, then duplicated into two references, then each reference is popped off for assignment).
This just adds iterable unpacking to the mix; expanding your code the same way, it would look like:
__specialtmp = "AB"
a = __specialtmp # Assigns original value to a
b, c = __specialtmp # Unpacks string as iterable of its characters, assigning "A" to b, and "B" to c
This won't always work mind you; if the thing being unpacked is an iterator, and you assign to the unpacked names first, the iterator will be exhausted and nothing useful will be available for the second assignment:
b, c = [*a] = iter("AB")
This unpacks "A" to b, and "B" to c, but when it gets to a, which in plain [*a] = iter("AB") would become ["A", "B"] (the star syntax capturing "remaining" values to a list), in this case, the iterator gets exhausted populating b and c and a gets nothing (the empty list, []).
Point is, while this trick works, I wouldn't recommend it in general. Initializing multiple names to the same immutable value is fine, but it's liable to bite you otherwise.
Such a cool question! Makes a lot of fun! :) Can be used at interviews :)
Ok, here we are
>>> b = "1984"
>>> a = b, c = "AB"
>>> print((a,b,c))
('AB', 'A', 'B')
>>> a = (b, c) = "AB"
>>> print((a,b,c))
('AB', 'A', 'B')
>>>
In python for multiple assignments, you can omit (...) and it looks like python parses this line similar to 2 lines
a = "AB"
b, c = "AB" # which is equal to (b, c) = "AB"
Some more examples
>>> a = b, c = "AB"
>>> print((a,b,c))
('AB', 'A', 'B')
>>> a = (b, c) = "AB"
>>> print((a,b,c))
('AB', 'A', 'B')
>>> a = "AB"
>>> b, c = "AB"
>>> print((a,b,c))
('AB', 'A', 'B')
>>>
It works using lists a well :)
>>> a = [b, c] = 'AB'
>>> print((a,b,c))
('AB', 'A', 'B')
>>>
Some more examples:
https://www.geeksforgeeks.org/unpacking-a-tuple-in-python/
https://treyhunner.com/2018/03/tuple-unpacking-improves-python-code-readability/
Let's make this a little simpler. Let's look at the following case
>>> b = 1
>>> b, c = (0, 2)
>>> print(b, c)
0, 2
Is it surprising that b is 0 and not 1? It shouldn't be since we assigning b to 0 and c to 2 when calling b, c = (0, 2) thanks to tuple unpacking.
Now to address the other part of the gotcha, let's take this example
>>> b = 1
>>> a = b = 0
>>> print (b)
0
Is it again surprising that b is 0 and not 1? Again, it shouldn't be since when calling a = b = 0, we've assigned both a and b to 0 with multiple assignment.
So coming back to the gotcha, the a = b, c = "AB" is just a combination of these two behaviors. b, c = "AB" will unpack "A" to b and "B" to c, and we're also assigning "AB" to a. While it looks like we're assigning a = b, we're really just doing the following two lines
>>> b = "1984"
>>> b, c = "AB"
>>> a = "AB"
Hopefully this breaks down where the tuple unpacking is happening and where the assignment is happening, and that it's not as confusing of a gotcha as it might look.

How to combine list and individual elements into one tuple in python?

I have a few variables in python3:
a = 1
b = [2,3,4]
c = 5
I want to get a tuple which is from above variables, like: (1,2,3,4,5)
what is the easiest way to do that in python3?
Creating a tuple in Python is as simple as putting the stuff you need in a tuple in parentheses:
my_tuple = (1, 2, 3)
a = 1
b = 2
c = 3
another_tuple = (a, b, c) # also works with variables, or anything else with a value
And if what you want in the tuple is in something else that can be unpacked, like a list or a tuple itself, you can use the unpacking operator * to unpack it into the new tuple:
a = 1
b = [2,3,4]
c = 5
my_tuple = (a, *b, c)
Not your question, but note that you can also get stuff from a tuple without using the * operator, as it's implied in an assignment statement:
x, _, z = my_tuple # continued from before
In this example, what was in a (1) is now also in x and what was in c also in z. What was in b and in the second position of the tuple gets discards (that's what the underscore _ here means, "don't care".)
You use the unpack operator in cases where you explicitly need to unpack and you're constructing some new variable, or need the elements of the tuple separately where they could also be used as a tuple. For example, when calling a function:
a_tuple = ('Hi there', 'John')
def greeting(phrase='Hello', name='world'):
print(f'{phrase}, {name}!')
greeting(*a_tuple)
In this example, calling greeting as greeting(a_tuple) would give you the very nasty result of ('Hi there', 'John'), world!, clearly not what you want, but you can still use the tuple with the unpack operator.
And the other obvious example is one like the one solving OP's question.
Simply create a new tuple as shown below.
newTuple=(a, *b, c)
Note: *b unpacks list b into variables and add each variable to indexes of newTuple
One of the ways is shown below
from functools import reduce
import operator
# create a bunch of lists, reduce them to one list and finally convert to tuple
result = tuple(reduce(operator.add, ([a], b, [c])))
print(result)
This is the most pythonic solution:
a = 1
b = [2,3,4]
c = 5
res = a,*b,c
# output: (1,2,3,4,5)
Note: To create the new tuple, round brackets are not necessary in Python3

Two Basic Python Dictionary Questions

Question 1.)
for a,a in dict.items():
print a
Why does the above print the value, and if i print a,a - obviously both values are printed twice. If I had typed for a,b I would be iterating (key,value) pairs so I would logically think I am now iterating over (key,key) pairs and would therefore print key rather than value. Sorry for the basic questions just playing around in interpreter and trying to figure stuff out.
Question 2.)
for key,value in dict.items():
print "%s is the key that corresponds to the value %s"%(key,value)
The above example makes sense to me, but why does:
for key in dict.items():
print "%s is the key that corresponds to the value %s"%(key)
produce the same result? Is it because even though we aren't unpacking the tuple into two separate variables in the for statement, it is returned as (key,value) in the string formatter through the key call - basically making %(key) = (key,value)?
Q1:
Consider the following:
>>> d = {"a": 1, "b": 2, "c": 3}
>>> xs = d.items()
>>> a, a = xs[0]
>>> print a, a
1 1
This is effectively what is happening. You are unpacking each (key, value) pair into (a, a) effectively ending up with (value, value). The reference to the key is lost.
This is the same as:
>>> a, a = 1, 2
>>> print a, a
2, 2
What happens here in the interpreter during the unpacking:
a becomes 1.
a becomes 2.
Q2:
Consider the following:
>>> a, b = ("a", 1)
>>> print "%s=%s" % (a, b)
a, 1
>>> x = ("a", 1)
>>> print "%s=%s" % x
a, 1
The two are effectively the same. Since your string interpolation is expecting a tuple of two both (a, b) and x (a tuple of 2 values) effectively satisfies this.
You right. key is now tuple (k,v) and "..."% expect tuple with arguments. And ((k,v)) ( in ".." % ((k,v)) ) doesn't give "tuple in tuple" but only "single-level" tuple - like (1) doesn't give tuple but integer 1 - so you have "..." % (k,v)
d={1:2,4:5,6:7,8:8}
for a,a in d.items():
print(a,a)
In dictionary ,
a,a runs with (key,value)
And ends up with (value,value)
Output:
Starts with (1,2) ends with (2,2)
Starts with (4,5) ends with (5,5)
Starts with (6,7) ends with (7,7)
Starts with (8,8) ends with (8,8)
So Actual Output:
2 2
5 5
7 7
8 8

Categories