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
I'm learning about logic gates and was messing around with them when I found something interesting. This is my code:
for i in range (4):
p = math.floor(i/2)
q = i % 2
a = p and q
b = not(not(p or q) or not(q) or not(p))
print(str(p) +"\t"+ str(q) + "\t"+ str(a)+"\t"+str(b))
And this is the result:
0 0 0 False
0 1 0 False
1 0 0 False
1 1 1 True
Why doesn't Python either always print a 1 or a 0 or always print True or False?
math.floor returns a number.
Operators between two numbers (such as modulo) also return numbers.
not returns a bool, not an int
Because not has to create a new value, it returns a boolean value regardless of the type of its argument - spec
If you want it to be an int (and simplify the print statement)
print('\t'.join(map(str, [p, q, a, int(b)])
The not unary operator always returns a bool value. It needs to, as it needs to return the boolean inverse of whatever value it was passed, which is always a new value (not its argument).
The real odd part of your chart is actually the third column, which shows that the and operator does return numbers if it's passed them on both sides. You might expect and to be a boolean operator just like not, but it's slightly different. The reason is that and always returns one of its arguments. If the first argument is falsey, that value is what is returned. If the first argument is truthy, the second argument is returned.
You can see this if you test with values other than just 0 and 1:
print(3 and 2) # prints 2
print([] and [1,2]) # prints []
The or operator also does this (in a slightly different manner), but that's covered up in your calculation of b by the not calls.
This behavior of and and or is called short-circuiting. It's big advantage is that it lets Python avoid interpreting expressions it doesn't need to get the value of the boolean expression. For instance, this expression will not call the slow_function:
result = falsey_value and slow_function()
It also lets you guard expressions that would cause exceptions if they were evaluated:
if node is not None and node.value > x:
...
If the node variable is None in that code, evaluating node.value would give an AttributeError (since None doesn't have a value attribute). But because and short circuits, the first expression prevents the second expression from being evaluated when it's invalid.
I have a small snippet of code with two functions in it.
I want to call the first function if it receives a response then perform a function on that response. Then assign the result to another variable.
In a verbose way it looks like:
result = get_something()
if result:
answer = transform(result)
alternatively I could do
if get_something():
answer = transform(get_something())
but that requires calling the first function twice
is there a way to do all of this on one line a bit like a ternary (maybe as a lambda)
answer = transform(result) if get_something() else None
Obviously in the above there is nothing to state what result is but I need to say basically where result = get_something()
I can do that in a list comprehension but that seems a bit dumb
answer = [transform(x) for x in [get_something()] if x][0]
In the latest Python version (Python 3.8) there's a new assignment that may be useful for you, :=:
There is new syntax := that assigns values to variables as part of a larger expression. It is affectionately known as “walrus operator” due to its resemblance to the eyes and tusks of a walrus.
if (n := len(a)) > 10:
print(f"List is too long ({n} elements, expected <= 10)")
In this example, the assignment expression helps avoid calling len() twice:
We can in Python 3.8 with assignment expressions:
if (result := get_something()) is not None:
# do something with result
Although I don't fully understand the reasons for doing things this way (which is less clear than any of the others), here's an example using lambda:
>>> def get_something(flag): # Added the flag argument, to mimic different return values
... return 5 if flag else None
...
>>> answer = (lambda func, arg: func(arg) if arg else None)(int, get_something(True))
>>> answer
5
>>> answer = (lambda func, arg: func(arg) if arg else None)(int, get_something(False))
>>> answer
>>>
I know the conditional expression in Python is X if C else Y, but I got some problems in using it.
I have two codes to be compared.
Code 1:
def fun(p):
if len(p) >= 2:
p[1] = 'Ok'
else:
p.append('Ok')
p = [1]
fun(p)
print p
Output of code 1:
[1, 'Ok']
Code 2:
def fun(p):
(p[1] = 'Ok') if (len(p) >= 2) else p.append('OK')
p = [1]
fun(p)
print p
Output of code 2:
(p[1] = 'Ok') if (len(p) >= 2) else p.append('OK')
^
SyntaxError: invalid syntax
I know in code 1, whose format is "if C : X else: Y", the evaluation order is:
C
X
Y
Code 2 throws a syntax error, the reason may be p[1] doesn't exist. So I guess the format "X if C else Y" is evaluated as follows:
X
C
Y
But that is only my guess. does anyone know the real reason why code 2 is wrong while code 1 is right?
The reason you have a SyntaxError is because Python differentiates between statements and expressions.
Assignments, like
p[1] = 'Ok'
are statements and can't be part of an expression, including the conditional expression. See What is the difference between an expression and a statement in Python? for more info.
Order of evaluation doesn't come into it -- SyntaxErrors happen before any code is evaluated, when its being parsed.
In both if statements and conditional expressions, The order of evaluations is ether
Condition
True Statement
or
Condition
False Statement
So, in
if condition:
true_statement
else:
false_statement
or
true_statement if condition else false_statement
only the true or the fase statement is evaluated, depending on the truthiness of the condition.
As Dan D. points out, you can't have statements in expression contexts.
In the interest of learning -- I do not recommend doing it this way -- I'll show you how you could use the inline X if C else Y to accomplish what you're attempting to do:
def fun(p):
p.__setitem__(1, 'Ok') if len(p) >= 2 else p.append('OK')
As you can see, your fist version is much more readable.
Assignment is no expression (others pointed that out). Hence the syntax error.
Regarding your question concerning evaluation order:
In if-else expressions(!) first the condition operand is evaluated, then either the then-operand (leftmost) or the else-operand (rightmost) is evaluated. The other operand is not evaluated.
In or-expressions the operands are evaluated from leftmost to rightmost (a or b or c or d or ...) but only until one is found whose Boolean value is True. The remaining expressions are not evaluated.
In and-expressions the operands are evaluated from leftmost to rightmost (a and b and c and d and ...) but only until one is found whose Boolean value is False. The remaining expressions are not evaluated.
Inner expressions are evaluated before outer expressions (of course): third(second(first()))
In all other cases (a() + b(), f(g(), h()), a[b[c]] = d() + e) the order is defined as specified. Typically it's left-to-right except for assignment (where it is right before left).
What does it mean when it's like this:
self.something += ('somethin',)
What does "+=" mean and what does the comma mean?
The expression a += b is shorthand for a = a + b, where a and b can be numbers, or strings, or tuples, or lists (but both must be of the same type).
The comma in ('x',) means that this is a tuple of a single element, 'x' . If the comma is absent, is just an 'x' between parenthesis.
+= is addition and assignment into one (sometimes referred to as iadd or in-place add). It is the same as a = a + x
a = 4
a += 5 # add 5 to a, and assign the result into a
b = 4
b = b + 5 # this does the same thing as +=
print a # prints out 9
print b # prints out 9
You can also do other operations in this style, such as -=, *=, /=, &= (bitwise and), |= (bitwise or), ^= (bitwise xor), %= (mod), **= (exponent).
('something',) is a tuple. ('something') (without the comma) is using the parenthesis in grouping, kind of like ('some' + 'thing') or (a + b). In order to differentiate between the one-member tuple and the grouping syntactically, Python uses a comma.
Python has an operator to assign value to a name and it's =.
The language also support many other operators like +, -, ** for operations defined in special methods of your objects.
Although + is the math sign to add things, it can be customized to do whatever you want.
Sometimes you want to make an operation and store it using the same name. For these situations you have in-place operators that are just the normal operators you're use to plus the = sign.
For immutable objects (numbers, strings, tuples,...) you can't have in-place changes because... they're immutable. So, the in-place methods do exactly the same thing the normal method followed by an assignment.
For mutable objects the difference is much clear:
In-place add:
>>> a = []
>>> b = a
>>> b += [1,2]
>>> a
[1, 2]
Add and assign:
>>> a = []
>>> b = a
>>> b = b + [1,2]
>>> a
[]
See? The object itself was transformed with the in-place add for lists, but, on the other case, a new object was created.
For your other question, the comma is the tuple separator.
a = (1) # Just number 1 inside parenthesis
a = (1,) # A tuple with one element
results=[]
for x in range(5):
results += '$'
print(results)
output : ['$', '$', '$', '$', '$']
this code behaves differently from typical += operator , as you can see here it has generated list with $ sign inside it.
its is not like we think results = results+ '$' this code will throw you an error.
What actually happens is += operator works like .extend() in lists.
Consider the following Python3 program:
a = [0, 0]
i = 0
a[i] = i = 1
print(a, i)
a = [0, 0]
i = 0
i = a[i] = 1
print(a, i)
I expected the output to be:
[0, 1] 1
[1, 0] 1
But instead I got:
[1, 0] 1
[0, 1] 1
My question is: is there anything in the Python language specification about associativity of the assignment operator, or is the behavior for the above example undefined?
All I was able to find is that expressions are evaluated form left to right, except that r-value is evaluated first in case of assignment, but that doesn't help.
Short answer: the code is well defined; the order is left-to-right.
Long answer:
First of all, let's get the terminology right. Unlike in some other languages, assignment in Python is a statement, not an operator. This means that you can't use assignment as part of another expression: for example i = (j = 0) is not valid Python code.
The assignment statement is defined to explicitly permit multiple assignment targets (in your example, these are i and a[i]). Each target can be a list, but let's leave that aside.
Where there are multiple assignment targets, the value is assigned from left to right. To quote the 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.
Just to make it clear, since I struggled myself to understand this. The statement:
a = b = c = ... = E
is equivalent to
a = E
b = E
c = E
...