This question already has an answer here:
How does swapping of members in tuples (a,b)=(b,a) work internally?
(1 answer)
Closed 8 years ago.
i know that my will seem trivial,but i cant figure out why isn't 'stop' equal to zero, since 'start' value is already overwritten.But when i simulate the same scenario out of the function it does indeed overwrite it.Am i missing something here?
def interval(start, stop =None, step = 1 ):
'Imitates range() for step >0 '
if stop is None:
start, stop = 0, start #since start is already 0, why isn't stop as well?
print start,stop , step
result = []
i = start
while i< stop:
result.append(i)
i+=step
return result
This expression
start, stop = 0, start
will NOT be evaluated like this
start = 0
stop = start
but will be evaluated by pushing both the values to the stack and the top two values will be rotated (so that the values will be swapped) and the values are assigned back to start and stop. Lets consider,
a = 1
a, b = 0, a
If we look at the disassembled code
import dis
dis.dis(compile("a = 1; a, b = 0, a", "<string>", "exec"))
it would look like
1 0 LOAD_CONST 0 (1)
3 STORE_NAME 0 (a)
6 LOAD_CONST 1 (0)
9 LOAD_NAME 0 (a)
12 ROT_TWO
13 STORE_NAME 0 (a)
16 STORE_NAME 1 (b)
19 LOAD_CONST 2 (None)
22 RETURN_VALUE
The LOAD_* operations will load the content in to the stack. If you look at
6 LOAD_CONST 1 (0)
9 LOAD_NAME 0 (a)
the values, 0 and the value in a are pushed to the stack and then ROT_TWO, rotates the values in the top two positions of the stack. And finally
13 STORE_NAME 0 (a)
16 STORE_NAME 1 (b)
assigns the rotated values to a and b respectively.
Related
This question already has answers here:
"is" operator behaves unexpectedly with integers
(11 answers)
Closed 6 months ago.
I have this code
l = 40000
m = 40000
print(type(l),type(m))
if l is m:
print("same")
else:
print("nope")
Taking reference from here I was hoping id to be different since the value are not falling in range of (-2,256). Please let me know if I am missing out on something
Myself using Python-3.8.3(32bit) on windows Platform
This is due to a simple optimization at the bytecode compilation stage; if the same constant appears in the same code more than once, it is created only once, and each use of the constant will refer to the same instance.
We can investigate the bytecode with the dis module:
>>> import dis
>>> dis.dis('l = 40000\nm=40000')
1 0 LOAD_CONST 0 (40000)
2 STORE_NAME 0 (l)
2 4 LOAD_CONST 0 (40000)
6 STORE_NAME 1 (m)
8 LOAD_CONST 1 (None)
10 RETURN_VALUE
>>> dis.dis('l = 40000\nm=40001')
1 0 LOAD_CONST 0 (40000)
2 STORE_NAME 0 (l)
2 4 LOAD_CONST 1 (40001)
6 STORE_NAME 1 (m)
8 LOAD_CONST 2 (None)
10 RETURN_VALUE
Note that in the first case where both constants are 40000, the two LOAD_CONST operations both load constant #0, but in the second case, 40000 is constant #0 and 40001 is constant #1.
Also, you will usually get different results if you do this on separate lines in the REPL, since each line in the REPL is compiled and executed as a separate code object, so they cannot share constants:
>>> l = 50000
>>> m = 50000
>>> id(l)
140545755966480
>>> id(m)
140545755966448
But if you do both in one line in the REPL, the same instance of the constant is used again, because it's just one line compiled to one code object, so the constant can be shared:
>>> p = 60000; q = 60000
>>> id(p)
140545755966576
>>> id(q)
140545755966576
I tried in the snippet below:
a, b = a[b] = {}, 5
print('a={0},b={1}'.format(a,b))
The IDE spits out the follows:
a={5: ({...}, 5)},b=5
I have tried S3DEV's advice and execute:
from dis import dis
dis('a, b = a[b] = {}, 5')
And it gives me the follows:
1 0 BUILD_MAP 0
2 LOAD_CONST 0 (5)
4 BUILD_TUPLE 2
6 DUP_TOP
8 UNPACK_SEQUENCE 2
10 STORE_NAME 0 (a)
12 STORE_NAME 1 (b)
14 LOAD_NAME 0 (a)
16 LOAD_NAME 1 (b)
18 STORE_SUBSCR
20 LOAD_CONST 1 (None)
22 RETURN_VALUE
But I still cannot understand why a[b] = a, 5 happened in the step 18 STORE_SUBSCR. Any further explanation?
This is an assignment statement with multiple target_list:s, for which case the docs say that the statement "assigns the single resulting object to each of the target lists, from left to right." Within each target_list, assignments also proceed left to right.
Thus, the statement is equivalent to
a = {}
b = 5
a[b] = a, 5
The reason that the last assignment is a[b]=a,5 and not a,b={},5 is that the value ({}, 5) is only evaluated once, so it's the same dict that gets used throughout. First, a is set to refer to that dict, then the dict — through a — is modified to refer to itself.
EDIT: Perhaps it is clearer to say that the statement is equivalent to
temp1 = {}
temp2 = 5
a = temp1
b = temp2
a[b] = temp1, temp2
Right before the last step, a and temp1 refer to the same object, which thus becomes self-referring after the last step.
This is not code I want to see in production. :)
This question already has an answer here:
Python Assignment Operator Precedence - (a, b) = a[b] = {}, 5
(1 answer)
Closed 4 years ago.
I found the assignment a = a[1:] = [2] in an article. I tried it in python3 and python2; it all works, but I don't understand how it works. = here is not like in C; C processes = by right to left. How does python process the = operator?
Per the language docs on assignment:
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.
In this case, a = a[1:] = [2] has an expression list [2], and two "target lists", a and a[1:], where a is the left-most "target list".
You can see how this behaves by looking at the disassembly:
>>> import dis
>>> dis.dis('a = a[1:] = [2]')
1 0 LOAD_CONST 0 (2)
2 BUILD_LIST 1
4 DUP_TOP
6 STORE_NAME 0 (a)
8 LOAD_NAME 0 (a)
10 LOAD_CONST 1 (1)
12 LOAD_CONST 2 (None)
14 BUILD_SLICE 2
16 STORE_SUBSCR
18 LOAD_CONST 2 (None)
20 RETURN_VALUE
(The last two lines of the disassembly can be ignored, dis is making a function wrapper to disassemble the string)
The important part to note is that when you do x = y = some_val, some_val is loaded on the stack (in this case by the LOAD_CONST and BUILD_LIST), then the stack entry is duplicated and assigned, from left to right, to the targets given.
So when you do:
a = a[1:] = [2]
it makes two references to a brand new list containing 2, and the first action is a STORE one of these references to a. Next, it stores the second reference to a[1:], but since the slice assignment mutates a itself, it has to load a again, which gets the list just stored. Luckily, list is resilient against self-slice-assignment, or we'd have issues (it would be forever reading the value it just added to add to the end until we ran out of memory and crashed); as is, it behaves as a copy of [2] was assigned to replace any and all elements from index one onwards.
The end result is equivalent to if you'd done:
_ = [2]
a = _
a[1:] = _
but it avoids the use of the _ name.
To be clear, the disassembly annotated:
Make list [2]:
1 0 LOAD_CONST 0 (2)
2 BUILD_LIST 1
Make a copy of the reference to [2]:
4 DUP_TOP
Perform store to a:
6 STORE_NAME 0 (a)
Perform store to a[1:]:
8 LOAD_NAME 0 (a)
10 LOAD_CONST 1 (1)
12 LOAD_CONST 2 (None)
14 BUILD_SLICE 2
16 STORE_SUBSCR
The way I understand such assignments is that this is equivalent to
temp = [2]
a = temp
a[1:] = temp
The resulting value of [2, 2] is consistent with this interpretation.
In Python, while assigning a value to a variable, we can either do:
variable = variable + 20
or
variable += 20.
While I do understand that both the operations are semantically same, i.e., they achieve the same goal of increasing the previous value of variable by 20, I was wondering if there are subtle run-time performance differences between the two, or any other slight differences which might deem one better than the other.
Is there any such difference, or are they exactly the same?
If there is any difference, is it the same for other languages such as C++?
Thanks.
Perhaps this can help you understand better:
import dis
def a():
x = 0
x += 20
return x
def b():
x = 0
x = x + 20
return x
print 'In place add'
dis.dis(a)
print 'Binary add'
dis.dis(b)
We get the following outputs:
In place add
4 0 LOAD_CONST 1 (0)
3 STORE_FAST 0 (x)
5 6 LOAD_FAST 0 (x)
9 LOAD_CONST 2 (20)
12 INPLACE_ADD
13 STORE_FAST 0 (x)
6 16 LOAD_FAST 0 (x)
19 RETURN_VALUE
Binary add
9 0 LOAD_CONST 1 (0)
3 STORE_FAST 0 (x)
10 6 LOAD_FAST 0 (x)
9 LOAD_CONST 2 (20)
12 BINARY_ADD
13 STORE_FAST 0 (x)
11 16 LOAD_FAST 0 (x)
19 RETURN_VALUE
You could do a loop a thousand or so times using a timer to compare perfomance, but the main difference is that one. I suppose binary add should be faster tho.
I have a line of code in my script that has both these operators chained together. From the documentation reference BOOLEAN AND has a lower precedence than COMPARISON GREATER THAN. I am getting unexpected results here in this code:
>>> def test(msg, value):
... print(msg)
... return value
>>> test("First", 10) and test("Second", 15) > test("Third", 5)
First
Second
Third
True
I was expecting Second or Third test to happen before the fist one, since > operator has a higher precedence. What am I doing wrong here?
https://docs.python.org/3/reference/expressions.html#operator-precedence
Because you are looking at the wrong thing. call (or function call) takes higher precendence over both and as well as > (greater than) . So first function calls occur from left to right.
Python will get the results for all function calls before either comparison happens. The only thing that takes precendence over here would be short circuiting , so if test("First",10) returned False, it would short circuit and return False.
The comparisons and and still occur in the same precendence , that is first the result of test("Second", 15) is compared against test("Third", 5) (please note only the return values (the function call already occured before)) . Then the result of test("Second", 15) > test("Third", 5) is used in the and operation.
From the documentation on operator precedence -
One way to see what's happening is to look at exactly how Python is interpreting this result:
>>> x = lambda: test("First", 10) and test("Second", 15) > test("Third", 5)
>>> dis.dis(x)
1 0 LOAD_GLOBAL 0 (test)
3 LOAD_CONST 1 ('First')
6 LOAD_CONST 2 (10)
9 CALL_FUNCTION 2
12 JUMP_IF_FALSE_OR_POP 42
15 LOAD_GLOBAL 0 (test)
18 LOAD_CONST 3 ('Second')
21 LOAD_CONST 4 (15)
24 CALL_FUNCTION 2
27 LOAD_GLOBAL 0 (test)
30 LOAD_CONST 5 ('Third')
33 LOAD_CONST 6 (5)
36 CALL_FUNCTION 2
39 COMPARE_OP 4 (>)
>> 42 RETURN_VALUE
If you do the same for 10 and 15 > 5, you get:
>>> x = lambda: 10 and 15 > 5
>>> dis.dis(x)
1 0 LOAD_CONST 1 (10)
3 JUMP_IF_FALSE_OR_POP 15
6 LOAD_CONST 2 (15)
9 LOAD_CONST 3 (5)
12 COMPARE_OP 4 (>)
>> 15 RETURN_VALUE