将绑定方法传递给函数的奇怪行为
Strange behavior passing bound method to function
我相信func(obj.attr)
和tmp = obj.attr; func(tmp)
应该是完全没有区别的。但是,下面的代码
class Cls(object):
#@staticmethod
def f(self):
pass
def who(obj):
print(id(obj) % 1000, end=' ')
obj = Cls()
n = 10
for i in range(n):
who(obj.f)
print('\n')
for i in range(n):
tmp = obj.f
who(tmp)
产生
736 736 736 736 736 736 736 736 736 736
736 216 736 216 736 216 736 216 736 216
我们可以看到第二个 for 循环产生了一个有趣的模式,而第一个没有。似乎分配绑定方法会改变绑定方法本身,但为什么将它传递给函数不会?
Python 会在您每次访问 f
时创建一个 new 绑定方法对象(参见 )。
在第一个循环中:
- 第一次迭代:
obj.f
创建绑定方法对象。
who
打印 id
- 没有更多对绑定方法对象的引用,因此解释器立即释放它
- 在以下迭代中:
obj.f
创建一个新的绑定方法对象。 解释器重新使用在之前的迭代中释放的内存。
who
打印 发生 的 id 以匹配先前的 id
- 再次释放对象
在第二个循环中:
- 第一次迭代
obj.f
创建一个新的绑定方法对象
- 它被分配给
tmp
(因此它现在有一个引用)
who
打印 id
- 未执行重新分配。
tmp
变量使对象保持活动状态
- 在接下来的迭代中
obj.f
创建一个新的绑定方法对象。 它不能重用旧地址,因为该对象仍然存在。
- 分配给
tmp
。 现在 tmp
的旧值没有更多引用,因此被释放。
who
打印 id,这是一个新的 。
- 同样没有重新分配发生
- 在接下来的迭代中
obj.f
创建一个新的绑定方法对象。现在旧地址再次可用,解释器决定重新使用它。
- 分配给
tmp
。 现在 tmp
的旧值没有更多引用,因此被释放。
who
打印 id,这是一个 old-old
- 同样没有重新分配发生
因此创建了一个在两个地址之间交替的循环。
通过使用更多变量,您可以创建更长的周期:
>>> tmp1 = obj.f
>>> tmp2 = tmp3 = tmp4 = tmp5 = None
>>> for i in range(20):
... tmp5 = tmp4
... tmp4 = tmp3
... tmp3 = tmp2
... tmp2 = tmp1
... tmp1 = obj.f
... who(tmp1)
...
864 896 560 280 288 864 896 560 280 288 864 896 560 280 288 864 896 560 280 288
或者,正如 Reblochon Masque 在他的 中提到的那样,使用 del
可以避免以下行为:
>>> for i in range(20):
... tmp = obj.f
... who(tmp)
... del tmp # forget the name
...
688 688 688 688 688 688 688 688 688 688 688 688 688 688 688 688 688 688 688 688
请注意,这是由于 CPython 的实现细节所致。在 Jython 或其他没有引用计数的 python 实现中,两个循环的行为可能几乎相同:显示十个不同的 ID。
不是答案,而是对 Bakuriu 答案的补充:
如果在继续迭代之前删除 temp,则会得到与情况 1 相同的结果:
(...)
for i in range(n):
tmp = obj.f
who(tmp)
del(tmp)
产生:
496 496 496 496 496 496 496 496 496 496
496 496 496 496 496 496 496 496 496 496
当然,实际的数字会反映所使用的系统在运行时处于什么状态。
我相信func(obj.attr)
和tmp = obj.attr; func(tmp)
应该是完全没有区别的。但是,下面的代码
class Cls(object):
#@staticmethod
def f(self):
pass
def who(obj):
print(id(obj) % 1000, end=' ')
obj = Cls()
n = 10
for i in range(n):
who(obj.f)
print('\n')
for i in range(n):
tmp = obj.f
who(tmp)
产生
736 736 736 736 736 736 736 736 736 736
736 216 736 216 736 216 736 216 736 216
我们可以看到第二个 for 循环产生了一个有趣的模式,而第一个没有。似乎分配绑定方法会改变绑定方法本身,但为什么将它传递给函数不会?
Python 会在您每次访问 f
时创建一个 new 绑定方法对象(参见
在第一个循环中:
- 第一次迭代:
obj.f
创建绑定方法对象。who
打印 id- 没有更多对绑定方法对象的引用,因此解释器立即释放它
- 在以下迭代中:
obj.f
创建一个新的绑定方法对象。 解释器重新使用在之前的迭代中释放的内存。who
打印 发生 的 id 以匹配先前的 id- 再次释放对象
在第二个循环中:
- 第一次迭代
obj.f
创建一个新的绑定方法对象- 它被分配给
tmp
(因此它现在有一个引用) who
打印 id- 未执行重新分配。
tmp
变量使对象保持活动状态
- 在接下来的迭代中
obj.f
创建一个新的绑定方法对象。 它不能重用旧地址,因为该对象仍然存在。- 分配给
tmp
。 现在tmp
的旧值没有更多引用,因此被释放。 who
打印 id,这是一个新的 。- 同样没有重新分配发生
- 在接下来的迭代中
obj.f
创建一个新的绑定方法对象。现在旧地址再次可用,解释器决定重新使用它。- 分配给
tmp
。 现在tmp
的旧值没有更多引用,因此被释放。 who
打印 id,这是一个 old-old- 同样没有重新分配发生
因此创建了一个在两个地址之间交替的循环。
通过使用更多变量,您可以创建更长的周期:
>>> tmp1 = obj.f
>>> tmp2 = tmp3 = tmp4 = tmp5 = None
>>> for i in range(20):
... tmp5 = tmp4
... tmp4 = tmp3
... tmp3 = tmp2
... tmp2 = tmp1
... tmp1 = obj.f
... who(tmp1)
...
864 896 560 280 288 864 896 560 280 288 864 896 560 280 288 864 896 560 280 288
或者,正如 Reblochon Masque 在他的 del
可以避免以下行为:
>>> for i in range(20):
... tmp = obj.f
... who(tmp)
... del tmp # forget the name
...
688 688 688 688 688 688 688 688 688 688 688 688 688 688 688 688 688 688 688 688
请注意,这是由于 CPython 的实现细节所致。在 Jython 或其他没有引用计数的 python 实现中,两个循环的行为可能几乎相同:显示十个不同的 ID。
不是答案,而是对 Bakuriu 答案的补充:
如果在继续迭代之前删除 temp,则会得到与情况 1 相同的结果:
(...)
for i in range(n):
tmp = obj.f
who(tmp)
del(tmp)
产生:
496 496 496 496 496 496 496 496 496 496
496 496 496 496 496 496 496 496 496 496
当然,实际的数字会反映所使用的系统在运行时处于什么状态。