2014-10-02 73 views
8

我正在Python 2.7中工作,我喜欢那个让我困惑的问题。为什么要为python对象设置绑定方法创建循环引用?

这是最简单的例子:

>>> class A(object): 
    def __del__(self): 
     print("DEL") 
    def a(self): 
     pass 

>>> a = A() 
>>> del a 
DEL 

这是确定的预期一样......我现在试图改变对象aa()方法,什么发生的是更改后它,我不能删除a更多:

>>> a = A() 
>>> a.a = a.a 
>>> del a 

只是做一些检查,我之前和分配之后打印a.a参考

>>> a = A() 
>>> print a.a 
<bound method A.a of <__main__.A object at 0xe86110>> 
>>> a.a = a.a 
>>> print a.a 
<bound method A.a of <__main__.A object at 0xe86110>> 

最后我用objgraph模块,试图理解为什么对象没有被释放:

>>> b = A() 
>>> import objgraph 
>>> objgraph.show_backrefs([b], filename='pre-backref-graph.png') 

pre-backref-graph.png

>>> b.a = b.a 
>>> objgraph.show_backrefs([b], filename='post-backref-graph.png') 

post-backref-graph.png

正如你可以在post-backref-graph.png图像有见是b中的__self__引用,因为自我r对我没有意义实例方法的推断应该被忽略(就像在赋值之前那样)。

有人可以解释为什么这种行为,我该如何解决它?

回答

3

当你写a.a,它有效地运行:

A.a.__get__(a, A) 

因为你没有访问预结合的方法,但类的方法正被 势必在运行时。

当你

a.a = a.a 

你有效的“缓存”的结合方法的行为。由于绑定方法具有对象的引用(显然,因为它必须将self传递给该函数),因此会创建一个循环引用。


所以我喜欢建模您的问题:

class A(object): 
    def __del__(self): 
     print("DEL") 
    def a(self): 
     pass 

def log_all_calls(function): 
    def inner(*args, **kwargs): 
     print("Calling {}".format(function)) 

     try: 
      return function(*args, **kwargs) 
     finally: 
      print("Called {}".format(function)) 

    return inner 

a = A() 
a.a = log_all_calls(a.a) 

a.a() 

您可以使用弱引用按需绑定内lof_all_calls,如:

import weakref 

class A(object): 
    def __del__(self): 
     print("DEL") 
    def a(self): 
     pass 

def log_all_calls_weakmethod(method): 
    cls = method.im_class 
    func = method.im_func 
    instance_ref = weakref.ref(method.im_self) 
    del method 

    def inner(*args, **kwargs): 
     instance = instance_ref() 

     if instance is None: 
      raise ValueError("Cannot call weak decorator with dead instance") 

     function = func.__get__(instance, cls) 

     print("Calling {}".format(function)) 

     try: 
      return function(*args, **kwargs) 
     finally: 
      print("Called {}".format(function)) 

    return inner 

a = A() 
a.a = log_all_calls_weakmethod(a.a) 

a.a() 

这实在是太丑了,所以我宁愿抽出来做一个weakmethod装饰:

import weakref 

def weakmethod(method): 
    cls = method.im_class 
    func = method.im_func 
    instance_ref = weakref.ref(method.im_self) 
    del method 

    def inner(*args, **kwargs): 
     instance = instance_ref() 

     if instance is None: 
      raise ValueError("Cannot call weak method with dead instance") 

     return func.__get__(instance, cls)(*args, **kwargs) 

    return inner 

class A(object): 
    def __del__(self): 
     print("DEL") 
    def a(self): 
     pass 

def log_all_calls(function): 
    def inner(*args, **kwargs): 
     print("Calling {}".format(function)) 

     try: 
      return function(*args, **kwargs) 
     finally: 
      print("Called {}".format(function)) 

    return inner 

a = A() 
a.a = log_all_calls(weakmethod(a.a)) 

a.a() 

完成!


FWIW,不仅是Python 3。4没有这些问题,它也有为您预制的WeakMethod

+0

好的......有一种方法可以避免这种情况?我应该缓存一些方法并在稍后恢复方法:这可能吗? – 2014-10-02 09:49:12

+0

这取决于你想要做什么。 – Veedrac 2014-10-02 09:49:41

+0

好的,我找到了解决方案:aa = types.MethodType(Aa,a,A) – 2014-10-02 09:54:37

4

Veedrac关于保持引用实例的绑定方法的答案只是答案的一部分。 CPython中的垃圾回收器知道如何检测和处理循环引用 - 除非某些对象是周期的一部分具有__del__方法,如下https://docs.python.org/2/library/gc.html#gc.garbage提到:有__del__()方法,是一个参考周期 的一部分

对象导致整个参考周期无法收集,包括 对象不一定在循环中,但只能从循环中访问。 Python不会自动收集这些循环,因为一般而言, Python不可能猜测运行方法的安全顺序。 (...)通常情况下,最好避免使用__del__()方法创建包含对象的循环,在这种情况下可以检查 垃圾,以验证没有创建这样的循环 。

IOW:删除您的__del__方法,你应该没问题。

编辑:WRT /您的评论:

我用它的对象为功能a.a = functor(a.a)。当测试 完成后,我想用原来的方法替换函子。

然后将溶液是简单明了:

a = A() 
a.a = functor(a.a) 
test(a) 
del a.a 

除非你明确地将其绑定,a已经没有“A”的实例属性附加伤害,所以它抬头的类并返回一个新的method实例(请参阅https://wiki.python.org/moin/FromFunctionToMethod了解更多信息)。然后这个method实例被调用,并且(通常)被丢弃。

+0

这不是一个解决方案....我需要__del__方法(原因在这里超出了范围),这个问题是唯一一个,我不知道如何解决其他弱引用效果不错 – 2014-10-02 10:51:19

+1

,它实际上回答了你的问题:“有人可以解释为什么这种行为,我该如何解决它?” - 你没有提到你需要'__del__',你的代码段并不意味着它有任何真正的用途;)现在,如果你看看我指出的链接,那么有更多的话题...... – 2014-10-02 11:03:00

+0

由于写在链接中你发布了最好的方法是不创建循环,我问的是“我不明白为什么该循环出生,如果有办法不创建那种循环”......但删除__del__不要删除循环,只是循环的副作用:) – 2014-10-02 11:30:41

1

至于为什么Python这样做。技术上全部对象包含循环引用,如果他们有方法。但是,如果垃圾收集器必须对对象方法进行显式检查以确保释放对象不会导致问题,则垃圾收集将花费更长时间。像这样Python将方法与对象的__dict__分开存储。因此,当您编写a.a = a.a时,您在该对象的a字段中使用了自己的方法。因此,对于防止对象被正确释放的方法有明确的参考。

问题的解决方案并不打算保留原始方法的“缓存”,只需在完成后删除阴影变量。这将淡化该方法并使其再次可用。

>>> class A(object): 
...  def __del__(self): 
...   print("del") 
...  def method(self): 
...   print("method") 
>>> a = A() 
>>> vars(a) 
{} 
>>> "method" in dir(a) 
True 
>>> a.method = a.method 
>>> vars(a) 
{'method': <bound method A.method of <__main__.A object at 0x0000000001F07940>>} 
>>> "method" in dir(a) 
True 
>>> a.method() 
method 
>>> del a.method 
>>> vars(a) 
{} 
>>> "method" in dir(a) 
True 
>>> a.method() 
method 
>>> del a 
del 

这里vars显示了在一个对象的属性__dict__。请注意0​​如何不包含对自身的引用,即使a.__dict__有效。 dir产生从给定对象可到达的所有属性的列表。在这里,我们可以看到对象上的所有属性和方法以及它的类和它们的基础的所有方法和属性。这表明a的绑定方法被存储在独立于a的属性存储位置。

+0

感谢您的回答,但真正的问题不是”为什么对象没有被销毁?“但为什么'a.a = a.a'创建一个循环引用。如果删除'__del__'覆盖并尝试在作业前后绘制图表,则会发现图形不同,第二个图形具有循环引用。 – 2014-10-03 13:50:56

+0

误解了这个问题。我已经完全改变了我的答案,并提出了一个新建议,并在此过程中自己学习了一些东西 – Dunes 2014-10-03 16:13:24

+0

THX:这正是我所说的“为什么要为python对象设置绑定方法创建循环引用?”。 Veedrac让我有办法处理与此有关的一些问题,但用我真正理解的答案。 – 2014-10-03 20:36:55

相关问题