How does ' data = data = ...' work in python? [duplicate] - python

I know about tuple unpacking but what is this assignment called where you have multiple equals signs on a single line? a la a = b = True
It always trips me up a bit especially when the RHS is mutable, but I'm having real trouble finding the right keywords to search for in the docs.

It's a chain of assignments and the term used to describe it is...
- Could I get a drumroll please?
Chained Assignment.
I just gave it a quite google run and found that there isn't that much to read on the topic, probably since most people find it very straight-forward to use (and only the true geeks would like to know more about the topic).
In the previous expression the order of evaluation can be viewed as starting at the right-most = and then working towards the left, which would be equivalent of writing:
b = True
a = b
The above order is what most language describe an assignment-chain, but python does it differently. In python the expression is evaluated as this below equivalent, though it won't result in any other result than what is previously described.
temporary_expr_result = True
a = temporary_expr_result
b = temporary_expr_result
Further reading available here on stackoverflow:
How do chained assignments work? python

#refp's answer is further supported with this output using the dis (disassembly) module:
>>> def a(x):
... g = h = x
...
>>> import dis
>>> dis.dis(a)
2 0 LOAD_FAST 0 (x)
3 DUP_TOP
4 STORE_FAST 1 (g)
7 STORE_FAST 2 (h)
10 LOAD_CONST 0 (None)
13 RETURN_VALUE
The RHS is retrieved and duplicated, then stored into the destination variables left-to-right (try this yourself with e = f = g = h = x).
Some other posters have been confused if the RHS is a function call, like a = b = fn() - the RHS is only evaluated once, and then the result assigned to each successive variable. This may cause unwanted sharing if the returned value is a mutable, like a list or dict.
For those using threading, it is useful to note that there is no "atomicity" implied by the chained assignment form over multiple explicit assignment statements - a thread switch could occur between the assignments to g and h, and another thread looking at the two of them could see different values in the two variables.
From the documentation, 7.2. Assignment statements, g and h being two target lists, x being the expression list:
assignment_stmt ::= (target_list "=")+ (expression_list | yield_expression)
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.

OK, "chained assignment" was the search term I was after, but after a bit more digging I think it's not strictly correct. but it is easier to search for than "a special case of the assignment statement".
The Wikipedia article senderle linked to says:
In Python, assignment statements are not expressions and thus do not
return a value. Instead, chained assignments are a series of
statements with multiple targets for a single expression. The
assignments are executed left-to-right so that i = arr[i] = f()
evaluates the expression f(), then assigns the result to the leftmost
target, i, and then assigns the same result to the next target,
arr[i], using the new value of i.
Another blog post says:
In Python, assignment statements do not return a value. Chained
assignment (or more precisely, code that looks like chained assignment
statements) is recognized and supported as a special case of the
assignment statement.
This seems the most correct to me, on a closer reading of the docs - in particular (target_list "=")+ - which also say
An assignment statement evaluates the expression list ... and assigns
the single resulting object to each of the target lists, from left to
right.
So it's not really "evaluated from right-most to left" - the RHS is evaluated and then assigned from left-most target to right - not that I can think of any real-world (or even contrived) examples where it would make a difference.

got bitten by python's Chained Assignment today, due to my ignorance. in code
if l1.val <= l2.val:
tail = tail.next = l1 # this line
l1 = l1.next
what I expected was
tail.next = l1
tail = tail.next
# or equivalently
# tail = l1
whereas I got below, which produce a self loop in the list, leave me in a endless loop, whoops...
tail = l1
tail.next = l1 # now l1.next is changed to l1 itself
since for a = b = c,
one way (python, for example) equivalent to
tmp = evaluate(c)
evaluate(a) = tmp
evaluate(b) = tmp
and have equal right operand for two assignment.
the other (C++, for example) equivalent to
evaluate(b) = evaluate(c)
evaluate(a) = evaluate(b)
since in this case a = b = c is basically
b = c
a = b
and two right hand operand could be different.
That why similar code works well in C++.

Related

Is it possible to add 2 values to 2 variables in one line?

Is it possible to add multiple values to multiple variables in one line?
Kind of similar to this:
a,b = (0,0)
But more like:
a,b += (1,2)
I know you can add them separately but I was wondering if there was a clean way to just do it one line.
Short answer: No. As per 7.2.1. Augmented assignment statements
An augmented assignment evaluates the target (which, unlike normal
assignment statements, cannot be an unpacking) and the expression
list, performs the binary operation specific to the type of assignment
on the two operands, and assigns the result to the original target.
The target is only evaluated once.
An augmented assignment expression like x += 1 can be rewritten as x = x + 1 to achieve a similar, but not exactly equal effect. In the
augmented version, x is only evaluated once. Also, when possible, the
actual operation is performed in-place, meaning that rather than
creating a new object and assigning that to the target, the old object
is modified instead.
Unlike normal assignments, augmented assignments evaluate the
left-hand side before evaluating the right-hand side. For example,
a[i] += f(x) first looks-up a[i], then it evaluates f(x) and performs
the addition, and lastly, it writes the result back to a[i].
With the exception of assigning to tuples and multiple targets in a
single statement, the assignment done by augmented assignment
statements is handled the same way as normal assignments. Similarly,
with the exception of the possible in-place behavior, the binary
operation performed by augmented assignment is the same as the normal
binary operations.
For targets which are attribute references, the same caveat about
class and instance attributes applies as for regular assignments.
For this case, the simplest way is to perform two statements:
a, b = 0, 0
a += 1
b += 2
You could also create a new tuple and do a normal unpacking assignment.
a, b = 0, 0
a, b = a + 1, b + 2
Or use a container that wraps the elements and supports an overriden augment assignment operators, such as numpy.
import numpy as np
a = np.array([0, 0])
a += np.array([1, 2])

Why does b+=(4,) work and b = b + (4,) doesn't work when b is a list?

If we take b = [1,2,3] and if we try doing: b+=(4,)
It returns b = [1,2,3,4], but if we try doing b = b + (4,) it doesn't work.
b = [1,2,3]
b+=(4,) # Prints out b = [1,2,3,4]
b = b + (4,) # Gives an error saying you can't add tuples and lists
I expected b+=(4,) to fail as you can't add a list and a tuple, but it worked. So I tried b = b + (4,) expecting to get the same result, but it didn't work.
The problem with "why" questions is that usually they can mean multiple different things. I will try to answer each one I think you might have in mind.
"Why is it possible for it to work differently?" which is answered by e.g. this. Basically, += tries to use different methods of the object: __iadd__ (which is only checked on the left-hand side), vs __add__ and __radd__ ("reverse add", checked on the right-hand side if the left-hand side doesn't have __add__) for +.
"What exactly does each version do?" In short, the list.__iadd__ method does the same thing as list.extend (but because of the language design, there is still an assignment back).
This also means for example that
>>> a = [1,2,3]
>>> b = a
>>> a += [4] # uses the .extend logic, so it is still the same object
>>> b # therefore a and b are still the same list, and b has the `4` added
[1, 2, 3, 4]
>>> b = b + [5] # makes a new list and assigns back to b
>>> a # so now a is a separate list and does not have the `5`
[1, 2, 3, 4]
+, of course, creates a new object, but explicitly requires another list instead of trying to pull elements out of a different sequence.
"Why is it useful for += to do this? It's more efficient; the extend method doesn't have to create a new object. Of course, this has some surprising effects sometimes (like above), and generally Python is not really about efficiency, but these decisions were made a long time ago.
"What is the reason not to allow adding lists and tuples with +?" See here (thanks, #splash58); one idea is that (tuple + list) should produce the same type as (list + tuple), and it's not clear which type the result should be. += doesn't have this problem, because a += b obviously should not change the type of a.
They are not equivalent:
b += (4,)
is shorthand for:
b.extend((4,))
while + concatenates lists, so by:
b = b + (4,)
you're trying to concatenate a tuple to a list
When you do this:
b += (4,)
is converted to this:
b.__iadd__((4,))
Under the hood it calls b.extend((4,)), extend accepts an iterator and this why this also work:
b = [1,2,3]
b += range(2) # prints [1, 2, 3, 0, 1]
but when you do this:
b = b + (4,)
is converted to this:
b = b.__add__((4,))
accept only list object.
From the official docs, for mutable sequence types both:
s += t
s.extend(t)
are defined as:
extends s with the contents of t
Which is different than being defined as:
s = s + t # not equivalent in Python!
This also means any sequence type will work for t, including a tuple like in your example.
But it also works for ranges and generators! For instance, you can also do:
s += range(3)
The "augmented" assignment operators like += were introduced in Python 2.0, which was released in October 2000. The design and rationale are described in PEP 203. One of the declared goals of these operators was the support of in-place operations. Writing
a = [1, 2, 3]
a += [4, 5, 6]
is supposed to update the list a in place. This matters if there are other references to the list a, e.g. when a was received as a function argument.
However, the operation can't always happen in place, since many Python types, including integers and strings, are immutable, so e.g. i += 1 for an integer i can't possibly operate in place.
In summary, augmented assignment operators were supposed to work in place when possible, and create a new object otherwise. To facilitate these design goals, the expression x += y was specified to behave as follows:
If x.__iadd__ is defined, x.__iadd__(y) is evaluated.
Otherwise, if x.__add__ is implemented x.__add__(y) is evaluated.
Otherwise, if y.__radd__ is implemented y.__radd__(x) is evaluated.
Otherwise raise an error.
The first result obtained by this process will be assigned back to x (unless that result is the NotImplemented singleton, in which case the lookup continues with the next step).
This process allows types that support in-place modification to implement __iadd__(). Types that don't support in-place modification don't need to add any new magic methods, since Python will automatically fall back to essentially x = x + y.
So let's finally come to your actual question – why you can add a tuple to a list with an augmented assignment operator. From memory, the history of this was roughly like this: The list.__iadd__() method was implemented to simply call the already existing list.extend() method in Python 2.0. When iterators were introduced in Python 2.1, the list.extend() method was updated to accept arbitrary iterators. The end result of these changes was that my_list += my_tuple worked starting from Python 2.1. The list.__add__() method, however, was never supposed to support arbitrary iterators as the right-hand argument – this was considered inappropriate for a strongly typed language.
I personally think the implementation of augmented operators ended up being a bit too complex in Python. It has many surprising side effects, e.g. this code:
t = ([42], [43])
t[0] += [44]
The second line raises TypeError: 'tuple' object does not support item assignment, but the operation is successfully performed anyway – t will be ([42, 44], [43]) after executing the line that raises the error.
Most people would expect X += Y to be equivalent to X = X + Y. Indeed, the Python Pocket Reference (4th ed) by Mark Lutz says on page 57 "The following two formats are roughly equivalent: X = X + Y , X += Y". However, the people who specified Python did not make them equivalent. Possibly that was a mistake which will result in hours of debugging time by frustrated programmers for as long as Python remains in use, but it's now just the way Python is. If X is a mutable sequence type, X += Y is equivalent to X.extend( Y ) and not to X = X + Y.
As it's explained here, if array doesn't implement __iadd__ method, the b+=(4,) would be just a shorthanded of b = b + (4,) but obviously it's not, so array does implement __iadd__ method. Apparently the implementation of __iadd__ method is something like this:
def __iadd__(self, x):
self.extend(x)
However we know that the above code is not the actual implementation of __iadd__ method but we can assume and accept that there's something like extend method, which accepts tupple inputs.

Variable assignment query in python

I am writing Fibonacci code in python. The below solution is mine.
While the other below solution is from python.org.
Can anyone tell me why it yields a different answer even though the logic of assigning the variables is the same?
Those two programs are not equivalent. The right hand side of the equals (=) is evaluated all together.
Doing:
a=b
b=a+b
Is different from:
a,b = b,a+b
This is in reality the same as:
c = a
a = b
b = b + c
Your example is actually covered on the Python documentation:
The first line contains a multiple assignment: the variables a and b simultaneously get the new values 0 and 1. On the last line this is used again, demonstrating that the expressions on the right-hand side are all evaluated first before any of the assignments take place. The right-hand side expressions are evaluated from the left to the right.
The lines
a = b # Assigns the value of 'b' to 'a'
b = a + b # Adds the new value of 'a' to 'b'
whereas,
a, b = b, a+b Assigns the value of b to a. Adds the existing value of a to b.
The reason it works in the second example is becasue the a=b doesn't evaluate until both are done. So when it gets to the b=a+b part, a is still its previous value. In your first example you are overwriting a before using it. In python when you declare variables in this way you are actually using them as tuples. This means that until the whole line is completed they retain their original values. Once the tuple is unpacked they are overwritten.
I see extra tabs in your solution and also logic of your program is wrong. As far as I understood by writing fib(5) you want 5th fibonacci in the series (which is 5) not a number which is less than 5 (which is 3).
a=b
b=a+b
and
a,b = b,a+b
are not equal.
Look at the code below.
def fibonacci(num):
a,b=0,1;
counter = 2;
while(a<=):
a,b = b,a+b
counter += 1
return b
print fibonacci(5)

Multiple-Target Assignments

I am reading a book about Python and there is a special part in the book about Multiple-Target Assignments. Now the book explains it like this:
but I dont see use of this. This makes no sense for me. Why would you use more variables?
Is there a reason to do this? What makes this so different from using: a='spam'and then printing out a 3 times?
I can only think of using it for emptying variables in one line.
A very good use for multiple assignment is setting a bunch of variables to the same number.
Below is a demonstration:
>>> vowels = consonants = total = 0
>>> mystr = "abcdefghi"
>>> for char in mystr:
... if char in "aeiou":
... vowels += 1
... elif char in "bcdfghjklmnpqrstvwxyz":
... consonants += 1
... total += 1
...
>>> print "Vowels: {}\nConsonants: {}\nTotal: {}".format(vowels, consonants, total)
Vowels: 3
Consonants: 6
Total: 9
>>>
Without multiple assignment, I'd have to do this:
>>> vowels = 0
>>> consonants = 0
>>> total = 0
As you can see, this is a lot more long-winded.
Summed up, multiple assignment is just Python syntax sugar to make things easier/cleaner.
It's mainly just for convenience. If you want to initialize a bunch of variables, it's more convenient to do them all on one line than several. The book even mentions that at the end of the snippet that you quoted: "for example, when initializing a set of counters to zero".
Besides that, though, the book is actually wrong. The example shown
a = b = c = 'spam'
is NOT equivalent to
c = 'spam'
b = c
a = b
What it REALLY does is basically
tmp = 'spam'
a = tmp
b = tmp
c = tmp
del tmp
Notice the order of the assignments! This makes a difference when some of the targets depend on each other. For example,
>>> x = [3, 5, 7]
>>> a = 1
>>> a = x[a] = 2
>>> a
2
>>> x
[3, 5, 2]
According to the book, x[1] would become 2, but clearly this is not the case.
For further reading, see these previous Stack Overflow questions:
How do chained assignments work?
What is this kind of assignment in Python called? a = b = True
Python - are there advantages/disadvantages to assignment statements with multiple (target list "=") groups?
And probably several others (check out the links on the right sidebar).
You might need to initialize several variables with the same value, but then use them differently.
It could be for something like this:
def fibonacci(n):
a = b = 1
while a < n:
c = a
a = a + b
b = c
return a
(variable swapping with tuple unpacking ommited to avoid confusion as with the downvoted answer)
An important note:
>>> a = b = []
is dangerous. It probably doesn't do what you think it does.
>>> b.append(7)
>>> print(b)
[7]
>>> print(a)
[7] # ???????
This is due to how variables work as names, or labels, in Python, rather than containers of values in other languages. See this answer for a full explanation.
Presumably you go on to do something else with the different variables.
a = do_something_with(a)
b = do_something_else_with(b)
#c is still 'spam'
Trivial example and the initialization step questionably didn't save you any work, but it's a valid way to code. There are certainly places where initializing a significant number of variables is needed, and as long as they're immutable this idiom can save space.
Though as the book pointed out, you can only use this type of grammar for immutable types. For mutable types you need to explicitly create multiple objects:
a,b,c = [mutable_type() for _ in range(3)]
Otherwise you end up with surprising results since you have three references to the same object rather than three objects.

idiomatic python, manage default arguments in functions

I usually encounter that most of the people manage default arguments values in functions or methods like this:
def foo(L=None):
if L is None:
L = []
However i see other people doing something like:
def foo(L=None):
L = L or []
I don't know if i a missing something but, why most of the people use the first approach instead the second? Are they equally the same thing?, seems that the second is clearer and shorter.
They are not equal.
First approach checks exactly, that given arg L is None.
Second checks, that L is true in python way. In python, if you check in condition the list, rules are the following:
List is empty, then it is False
True otherwise
So what's the difference between mentioned approaches? Compare this code.
First:
def foo(L=None):
if L is None:
L = []
L.append('x')
return L
>>> my_list = []
>>> foo(my_list)
>>> my_list
['x']
Second:
def foo(L=None):
L = L or []
L.append('x')
return L
>>> my_list = []
>>> foo(my_list)
>>> my_list
[]
So first didn't create a new list, it used the given list. But second creates the new one.
The two are not equivalent if the argument is a false-y value. This doesn't matters often, as many false-y values aren't suitable arguments to most functions where you'd do this. Still, there are conceivable situations where it can matter. For example, if a function is supposed to fill a dictionary (creating a new one if none is given), and someone passes an empty ordered dictionary instead, then the latter approach would incorrectly return an ordinary dictionary.
That's not my primary reason for always using the is None version though. I prefer it as it is more explicit and the fact that or returns one of its operands isn't intuitive to me. I like to forget about it as long as I can ;-) The extra line is not a problem, this is relatively rare.
Maybe they don't know of the second one? I tend to use the first.
Actually there is a difference. The second one will let L = [] if you pass anything that evaluates to Boolean false. 0 empty string or others. The first will only do that if no L is passed or it was passed as None.

Categories

Resources