Four confusing references in Python

Posted by saraadmin on Thu, 26 Dec 2019 08:47:07 +0100

First: differences in execution timing

1.

1 array = [1, 8, 15]
2 g = (x for x in array if array.count(x) > 0)
3 array = [2, 8, 22]

 

Output:

1 >>> print(list(g))
2 [8]

 

2.

1 array_1 = [1,2,3,4]
2 g1 = (x for x in array_1)
3 array_1 = [1,2,3,4,5]
4 
5 array_2 = [1,2,3,4]
6 g2 = (x for x in array_2)
7 array_2[:] = [1,2,3,4,5]

 

Output:

1 >>> print(list(g1))
2 [1,2,3,4]
3 
4 >>> print(list(g2))
5 [1,2,3,4,5]

 

Explain

  • In a generator expression, the in clause is executed at declaration time and the condition clause is executed at run time

  • So before running, array has been reassigned to [2, 8, 22], so for the previous 1, 8 and 15, only count(8) is greater than 0, so the generator will only generate 8

  • In the second part, the output difference between g1 and g2 is caused by the way that the variables array 1 and array 2 are reassigned

  • In the first case, array_1 is bound to the new object [1,2,3,4,5], because the in clause is executed at declaration time, it still references the old object [1,2,3,4] (not destroyed)

  • In the second case, the slice assignment of array 2 updates the same old object [1,2,3,4] to [1,2,3,4,5] in place. Therefore, g2 and array 2 still refer to the same object (this object has now been updated to [1,2,3,4,5])

Second: unexpected is

Here is a very famous example on the Internet

 1 >>> a = 256
 2 >>> b = 256
 3 >>> a is b
 4 True
 5 
 6 >>> a = 257
 7 >>> b = 257
 8 >>> a is b
 9 False
10 
11 >>> a = 257; b = 257
12 >>> a is b
13 True

 

Explain:

The difference between is and = =

  • The is operator checks whether two operands refer to the same object (that is, it checks whether two operands are the same)

  • ==Operator to compare two operands for equality

    So is for the same reference and = = for the same value. The following example can illustrate this point well,

1 >>> [] == []
2 True
3 >>> [] is [] # The two empty lists are at different memory addresses.
4 False

 

256 is an existing object, but 257 is not

When you start Python, objects with values from - 5 to 256 have been assigned. These numbers are often used, so they will be prepared in advance

Python uses this method to create small integer pool to avoid frequent application and destruction of memory space

The current implementation reserves an array of integer objects for all integers between - 5 and 256. When you create an integer in this range, you only need to return the reference of the existing object. So it is possible to change the value of 1. I suspect that this behavior is undefined in Python.: -)

 1 >>> id(256)
 2 10922528
 3 >>> a = 256
 4 >>> b = 256
 5 >>> id(a)
 6 10922528
 7 >>> id(b)
 8 10922528
 9 >>> id(257)
10 140084850247312
11 >>> x = 257
12 >>> y = 257
13 >>> id(x)
14 140084850247440
15 >>> id(y)
16 140084850247344

 

Here the interpreter is not intelligent enough to realize that we have created an integer 257 when executing y = 257, so it creates another object in memory

When a and b are initialized in the same row with the same value, they point to the same object

 1 >>> a, b = 257, 257
 2 >>> id(a)
 3 140640774013296
 4 >>> id(b)
 5 140640774013296
 6 >>> a = 257
 7 >>> b = 257
 8 >>> id(a)
 9 140640774013392
10 >>> id(b)
11 140640774013488

 

  • When a and b are set to 257 on the same line, the Python interpreter creates a new object and then references the second variable at the same time. If you do it on different lines, it won't "know" that a 257 object already exists

  • This is a compiler optimization especially for interactive environment. When you enter two lines in the real-time interpreter, they will compile separately, so they will also optimize separately. If you try this example in. py file, you will not see the same behavior, because the file is compiled once

Third: Shadow array

1 # Let's initialize a variable first row
2 row = [""]*3 #row i['', '', '']
3 # And create a variable board
4 board = [row]*3

 

Output:

1 >>> board
2 [['', '', ''], ['', '', ''], ['', '', '']]
3 >>> board[0]
4 ['', '', '']
5 >>> board[0][0]
6 ''
7 >>> board[0][0] = "X"
8 >>> board
9 [['X', '', ''], ['X', '', ''], ['X', '', '']]

 

Have we ever assigned three X's?

Explain:

When we initialize the row variable, the following figure shows the situation in memory.

When initializing board by multiplying row, the situation in memory is as shown in the following figure (each element board[0], board[1] and board[2] refer to the same list as row.)

We can avoid this situation by generating a board without using the variable row

1 >>> board = [['']*3 for _ in range(3)]
2 >>> board[0][0] = "X"
3 >>> board
4 [['X', '', ''], ['', '', ''], ['', '', '']]

 

Fourth: chaotic output

 1 #python Learning group 592539176
 2 funcs = []
 3 results = []
 4 for x in range(7):
 5     def some_func():
 6         return x
 7     funcs.append(some_func)
 8     results.append(some_func()) # Notice that the function is executed here
 9 
10 funcs_results = [func() for func in funcs]

 

Output:

1 >>> results
2 [0, 1, 2, 3, 4, 5, 6]
3 >>> funcs_results
4 [6, 6, 6, 6, 6, 6, 6]

 

Even if the x value before adding some func to funcs in each iteration is different, all functions return 6

Another example

1 >>> powers_of_x = [lambda x: x**i for i in range(10)]
2 >>> [f(2) for f in powers_of_x]
3 [512, 512, 512, 512, 512, 512, 512, 512, 512, 512]

 

Explain:

  • When a function is defined inside a loop, if the function uses a loop variable in its body, the closure function will be bound to the loop variable instead of its value. Therefore, all functions are evaluated using the last value assigned to the variable

  • You can get the expected result by passing the loop variable to the function as a named variable. Why is this feasible? Because it will define a local variable again in the function

1 #python Learning group 592539176
2 funcs = []
3 for x in range(7):
4     def some_func(x=x):
5         return x
6     funcs.append(some_func)

 

Output:

1 >>> funcs_results = [func() for func in funcs]
2 >>> funcs_results
3 [0, 1, 2, 3, 4, 5, 6]

Topics: Python Lambda