Python中令人迷惑的4个引用
第一个:执行时机的差异
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]
说明
-
在生成器表达式中,
in
子句在声明时执行, 而条件子句则是在运行时执行. -
所以在运行前,
array
已经被重新赋值为[2, 8, 22]
, 因此对于之前的1
,8
和15
, 只有count(8)
的结果是大于0
的, 所以生成器只会生成8
. -
第二部分中
g1
和g2
的输出差异则是由于变量array_1
和array_2
被重新赋值的方式导致的. -
在第一种情况下,
array_1
被绑定到新对象[1,2,3,4,5]
, 因为in
子句是在声明时被执行的, 所以它仍然引用旧对象[1,2,3,4]
(并没有被销毁). -
在第二种情况下, 对
array_2
的切片赋值将相同的旧对象[1,2,3,4]
原地更新为[1,2,3,4,5]
. 因此g2
和array_2
仍然引用同一个对象(这个对象现在已经更新为[1,2,3,4,5]
).
第二个:出人意料的is
下面是一个在互联网上非常有名的例子.
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
说明:
is
和 ==
的区别
-
is
运算符检查两个运算对象是否引用自同一对象 (即, 它检查两个运算对象是否相同). -
==
运算符比较两个运算对象的值是否相等.因此
is
代表引用相同,==
代表值相等. 下面的例子可以很好的说明这点,
1 >>> [] == [] 2 true 3 >>> [] is [] # 这两个空列表位于不同的内存地址. 4 false
256
是一个已经存在的对象, 而 257
不是
当你启动python 的时候, 数值为 -5
到 256
的对象就已经被分配好了. 这些数字因为经常被使用, 所以会被提前准备好.
python 通过这种创建小整数池的方式来避免小整数频繁的申请和销毁内存空间.
当前的实现为-5到256之间的所有整数保留一个整数对象数组, 当你创建了一个该范围内的整数时, 你只需要返回现有对象的引用. 所以改变1的值是有可能的. 我怀疑这种行为在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
这里解释器并没有智能到能在执行 y = 257
时意识到我们已经创建了一个整数 257
, 所以它在内存中又新建了另一个对象.
当 a
和 b
在同一行中使用相同的值初始化时,会指向同一个对象.
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
-
当 a 和 b 在同一行中被设置为
257
时, python 解释器会创建一个新对象, 然后同时引用第二个变量. 如果你在不同的行上进行, 它就不会 "知道" 已经存在一个257
对象了. -
这是一种特别为交互式环境做的编译器优化. 当你在实时解释器中输入两行的时候, 他们会单独编译, 因此也会单独进行优化. 如果你在
.py
文件中尝试这个例子, 则不会看到相同的行为, 因为文件是一次性编译的.
第三个:影子数组
1 # 我们先初始化一个变量row 2 row = [""]*3 #row i['', '', ''] 3 # 并创建一个变量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', '', '']]
我们有没有赋值过3个 "x" 呢?
说明:
当我们初始化 row
变量时, 下面这张图展示了内存中的情况。
而当通过对 row
做乘法来初始化 board
时, 内存中的情况则如下图所示 (每个元素 board[0]
, board[1]
和 board[2]
都和 row
一样引用了同一列表.)
我们可以通过不使用变量 row
生成 board
来避免这种情况. (这个issue提出了这个需求.)
1 >>> board = [['']*3 for _ in range(3)] 2 >>> board[0][0] = "x" 3 >>> board 4 [['x', '', ''], ['', '', ''], ['', '', '']]
第四个:混乱的输出
1 #python学习群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()) # 注意这里函数被执行了 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]
即使每次在迭代中将 some_func
加入 funcs
前的 x
值都不相同, 所有的函数还是都返回6.
再换个例子
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]
说明:
-
当在循环内部定义一个函数时, 如果该函数在其主体中使用了循环变量, 则闭包函数将与循环变量绑定, 而不是它的值. 因此, 所有的函数都是使用最后分配给变量的值来进行计算的.
-
可以通过将循环变量作为命名变量传递给函数来获得预期的结果. 为什么这样可行? 因为这会在函数内再次定义一个局部变量.
1 #python学习群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]
上一篇: 初始css一
下一篇: python 实现简单的FTP