我不明白这个python __del__行为
有人可以解释为什么下面的代码行为的方式:
import types class Dummy(): def __init__(self, name): self.name = name def __del__(self): print "delete",self.name d1 = Dummy("d1") del d1 d1 = None print "after d1" d2 = Dummy("d2") def func(self): print "func called" d2.func = types.MethodType(func, d2) d2.func() del d2 d2 = None print "after d2" d3 = Dummy("d3") def func(self): print "func called" d3.func = types.MethodType(func, d3) d3.func() d3.func = None del d3 d3 = None print "after d3"
输出(注意d2的析构函数从来没有被调用过)是这个(python 2.7)
delete d1 after d1 func called after d2 func called delete d3 after d3
有没有办法“修复”代码,所以调用析构函数而不删除添加的方法? 我的意思是,放置d2.func = None的最佳位置是在析构函数中!
谢谢
基于最初的几个答案,我想澄清的是,我并没有问及使用__del__
的优点(或缺乏)。 我试图创build最短的函数来certificate我认为是非直观的行为。 我假设一个循环引用已经创build,但我不知道为什么。 如果可能的话,我想知道如何避免循环引用….
你不能假设__del__
永远不会被调用 – 它不是希望资源被自动释放的地方。 如果你想确保一个(非内存)资源被释放,你应该制作一个release()
或者类似的方法,然后明确地调用它(或者在下面的注释中用Thanatos指出的在上下文pipe理器中使用它)。
至less你应该仔细阅读__del__
文档 ,然后你可能不应该尝试使用__del__
。 (有关gc.garbage
的其他坏处,请参阅gc.garbage
文档 )
我提供了自己的答案,因为虽然我很欣赏避免__ del__的build议,但我的问题是如何使代码示例提供的工作正常。
简短版本:以下代码使用weakref来避免循环引用。 我以为在发布这个问题之前我已经尝试过了,但是我想我一定是做错了。
import types, weakref class Dummy(): def __init__(self, name): self.name = name def __del__(self): print "delete",self.name d2 = Dummy("d2") def func(self): print "func called" d2.func = types.MethodType(func, weakref.ref(d2)) #This works #d2.func = func.__get__(weakref.ref(d2), Dummy) #This works too d2.func() del d2 d2 = None print "after d2"
更长的版本 :当我发布这个问题时,我也search了类似的问题。 我知道你可以使用,而stream行的观点是__ del__是坏的 。
使用有意义,但只在某些情况下。 打开一个文件,阅读文件,closures文件就是一个很好的例子, 它是一个完美的解决scheme。 你已经去了一个需要对象的特定的代码块,并且你想清理对象和块的结尾。
数据库连接似乎经常被用作一个不适合使用的例子,因为您通常需要保留创build连接的代码段,并且在更多事件驱动(而不是顺序)的情况下closures连接。大体时间。
如果不是正确的解决scheme,我看到两个select:
- 确保__ del__正常工作(请参阅此博客 ,以获取有关weakref用法的更好描述)
- 程序closures时,您可以使用atexit模块来运行callback。 例如,请参阅此主题 。
虽然我试图提供简化的代码,但是我真正的问题是更多的事件驱动,所以这不是一个合适的解决scheme(对于简化的代码来说很好)。 我也想避免这个问题,因为我的程序可以长时间运行,我希望能够尽快执行清理工作。
所以,在这个特定的情况下,我发现它是使用weakref并防止循环引用会阻止__ del__工作的最佳解决scheme。
这可能是规则的一个例外,但也有使用weakref和__ del__是正确的实现,恕我直言。
可以使用with
运算符来代替del 。
http://effbot.org/zone/python-with-statement.htm
就像filetype对象一样,你可以这样
with Dummy('d1') as d: #stuff #d is guaranteed to be out of scope
del
不会调用__del__
del
你正在使用的方式删除一个局部variables。 当对象被销毁时__del__
被调用。 Python作为一种语言并不能保证它什么时候会销毁一个对象。
CPython作为Python的最常见的实现,使用引用计数。 结果del会经常按照你的预期工作。 但是,如果您有一个参考周期,它将不起作用。
d3 -> d3.func -> d3
Python不检测这个,所以不会马上清理它。 而不只是参考周期。 如果一个exception抛出,你可能还想调用你的析构函数。 但是,Python通常会保留局部variables作为追踪的一部分。
解决scheme不依赖于__del__
方法。 而是使用上下文pipe理器。
class Dummy: def __enter__(self): return self def __exit__(self, type, value, traceback): print "Destroying", self with Dummy() as dummy: # Do whatever you want with dummy in here # __exit__ will be called before you get here
这是保证工作,你甚至可以检查参数,看看你是否正在处理exception,并在这种情况下做一些不同的事情。
上下文pipe理器的完整示例。
class Dummy(object): def __init__(self, name): self.name = name def __enter__(self): return self def __exit__(self, exct_type, exce_value, traceback): print 'cleanup:', d def __repr__(self): return 'Dummy(%r)' % (self.name,) with Dummy("foo") as d: print 'using:', d print 'later:', d
在我看来,这件事的真正核心在于:
添加function是dynamic的(在运行时),并且不提前知道
我觉得你真的是一个灵活的方式来绑定不同的function,代表程序状态的对象。 也称为多态性。 Python做得很好,不是通过附加/分离方法,而是通过实例化不同的类。 我build议你再看看你的class级组织。 也许你需要从暂态对象中分离一个核心持久化数据对象。 使用has-a范式而不是is-a:每当状态发生变化时,您将核心数据包装在状态对象中,或者将新的状态对象分配给核心的属性。
如果你确定你不能使用这种Pythonic OOP,那么你仍然可以通过在类中定义所有的函数来开始处理,然后将它们绑定到其他的实例属性(除非你正在编译这些function可以从用户input中进行):
class LongRunning(object): def bark_loudly(self): print("WOOF WOOF") def bark_softly(self): pring("woof woof") while True: d = LongRunning() d.bark = d.bark_loudly d.bark() d.bark = d.bark_softly d.bark()
使用weakref
一个替代解决scheme是,只有在通过覆盖类的__getattr__
或__getattribute__
来调用函数时,才会将函数dynamic绑定到实例,以返回func.__get__(self, type(self))
而不仅仅是函数绑定的函数实例。 这就是在类上定义的函数的行为。 不幸的是(对于一些使用情况)python不会执行附加到实例本身的函数的相同逻辑,但是您可以修改它来执行此操作。 我遇到了绑定到实例的描述符的类似问题。 这里的性能可能不如使用weakref
,但它是一个选项,将透明地工作的任何dynamic分配函数只使用python内build。
如果你经常这样做,你可能需要一个自定义的元类来dynamic绑定实例级的函数。
另一种方法是将函数直接添加到类中,然后在调用时正确执行绑定。 对于大量的用例来说,这会引起一些头痛的问题,即对函数进行正确的命名空间,以免碰撞。 但是,实例ID可以用于此,因为在程序生命周期中,cPython中的id不能保证是唯一的,所以你需要思考这一点,以确保它适用于你的用例…在特别是,当对象超出范围时,可能需要确保删除类函数,因此其ID /内存地址可以再次使用。 __del__
是完美的:)。 或者,你可以清除所有的方法命名空间的对象创build对象(在__init__
或__new__
)。
另一种select(而不是捣乱python魔术方法)是显式地添加一个方法来调用你的dynamic绑定函数。 这有缺点,你的用户不能使用普通的Python语法来调用你的函数:
class MyClass(object): def dynamic_func(self, func_name): return getattr(self, func_name).__get__(self, type(self)) def call_dynamic_func(self, func_name, *args, **kwargs): return getattr(self, func_name).__get__(self, type(self))(*args, **kwargs) """ Alternate without using descriptor functionality: def call_dynamic_func(self, func_name, *args, **kwargs): return getattr(self, func_name)(self, *args, **kwargs) """
为了使这个post完成,我会显示你的weakref
选项:
import weakref inst = MyClass() def func(self): print 'My func' # You could also use the types modules, but the descriptor method is cleaner IMO inst.func = func.__get__(weakref.ref(inst), type(inst))