【问题标题】:Strange behavior passing bound method to function将绑定方法传递给函数的奇怪行为
【发布时间】:2015-11-24 02:29:48
【问题描述】:

我相信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 methods variable-assignment argument-passing


    【解决方案1】:

    每次你访问f时,Python都会创建一个new绑定的方法对象(见Does Python really create all bound method for every new instance?)。

    在第一个循环中:

    • 在第一次迭代中:
      • 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,这是一个老旧的
      • 再一次没有发生解除分配

    因此创建了一个循环,在两个地址之间交替。

    通过使用更多变量,您可以创建更长的循环:

    >>> 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 在他的 answer 中提到的那样,使用 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。

    【讨论】:

      【解决方案2】:

      不是答案,而是对 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
      

      当然,实际数字会反映系统在运行时所处的状态。

      【讨论】:

        猜你喜欢
        • 2018-05-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-04-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多