我不了解这种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__
的优点(或不足之处)。我试图创建最短的函数来证明我认为是非直觉的行为。我假设已经创建了循环引用,但是我不确定为什么。如果可能的话,我想知道如何避免使用循环引用。     

解决方法

您不能假设会调用
__del__
-它不是一个希望自动分配资源的地方。如果要确保释放(非内存)资源,则应使用
release()
或类似方法,然后显式调用它(或在Thanatos在下面的注释中指出的上下文管理器中使用它)。 至少您应该非常仔细地阅读
__del__
文档,然后您可能不应该尝试使用
__del__
。 (有关
__del__
的其他不良信息,请参阅
gc.garbage
文档)     ,我提供自己的答案是因为,尽管我喜欢避免使用
__del__
的建议,但我的问题是如何使它在所提供的代码示例中正常工作。 简短版本:以下代码使用ѭ10来避免循环引用。我以为在发布问题之前已经尝试过此操作,但是我想我一定做错了什么。
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\"
较长版本: 当我发布问题时,我确实搜索了类似的问题。我知道您可以改用
with
,而普遍的看法是
__del__
不好。 使用
with
是有意义的,但仅在某些情况下才适用。打开文件,读取文件并将其关闭是一个很好的示例,其中ѭ12是一个很好的解决方案。您已经找到了需要对象的特定代码块,并且想要清理对象和代码块的结尾。 似乎经常使用数据库连接作为示例,但在使用t12 doesn时效果不佳,因为您通常需要离开创建连接的代码部分,并以更多的事件驱动(而不是顺序驱动)关闭连接) 大体时间。 如果
with
不是正确的解决方案,我会看到两种选择: 您确保
__del__
可以正常工作(有关详情,请参见此博客 描述的用法) 程序关闭时,使用
atexit
模块运行回调。例如,请参阅本主题。 当我尝试提供简化的代码时,我的真正问题更多是由事件驱动的,因此
with
不是一个合适的解决方案(
with
适合简化代码)。我也想避免使用ѭ19,因为我的程序可以长时间运行,并且希望能够尽快执行清理。 因此,在这种特定情况下,我发现它是使用ѭ10并防止会阻止ѭ2起作用的循环引用的最佳解决方案。 这可能是该规则的例外,但是在某些情况下,使用HO10ѭ和
__del__
是正确的实现,恕我直言。     ,您可以使用
with
运算符代替del。 http://effbot.org/zone/python-with-statement.htm 就像文件类型对象一样,您可以
with Dummy(\'d1\') as d:
    #stuff
#d is guaranteed to be out of scope
    ,
del
不叫
__del__
you29ѭ使用的方式将删除局部变量。销毁对象时会调用“ 2”。 Python作为一种语言无法保证何时销毁对象。 CPython是Python最常见的实现,它使用引用计数。因此,del通常会按您期望的那样工作。但是,如果您有参考循环,则该功能将无效。
d3 -> d3.func -> d3
Python无法检测到该错误,因此不会立即对其进行清理。而且它不只是参考周期。如果引发异常,则可能仍要调用析构函数。但是,Python通常会保留局部变量作为其回溯的一部分。 解决方案不取决于
__del__
方法。而是使用上下文管理器。
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
这样可以保证正常工作,您甚至可以检查参数以查看是否正在处理异常并在这种情况下执行其他操作。     ,上下文管理器的完整示例。
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
    ,在我看来,这件事的真正核心是在这里:   添加功能是动态的(在运行时),并且事先未知 我感觉到,您真正追求的是一种将不同功能绑定到表示程序状态的对象(也称为多态)的灵活方法。 Python做到的很好,不是通过附加/分离方法,而是通过实例化不同的类。我建议您再看一下班级组织。也许您需要将核心的持久数据对象与瞬态对象分开。使用has-a模式而不是is-a:每次状态更改时,您要么将核心数据包装在状态对象中,要么将新的状态对象分配给核心的属性。 如果确定不能使用这种pythonic OOP,则仍然可以通过定义类中的所有函数并随后将它们绑定到其他实例属性来另一种方法来解决问题(除非您\正在从用户输入中即时编译这些功能):
class LongRunning(object):
    def bark_loudly(self):
        print(\"WOOF WOOF\")

    def bark_softly(self):
        print(\"woof woof\")


while True:
    d = LongRunning()
    d.bark = d.bark_loudly
    d.bark()

    d.bark = d.bark_softly
    d.bark()
    ,使用
weakref
的另一种解决方案是仅在通过在类上重写
__getattr__
或ѭ40to来返回
func.__get__(self,type(self))
而不是仅将
func
绑定到实例的函数时,才将函数动态绑定到实例。这就是在类上定义的函数的行为方式。不幸的是(对于某些用例),python对实例本身附加的功能执行的逻辑不同,但是您可以对其进行修改以执行此操作。我对绑定到实例的描述符也遇到了类似的问题。这里的性能可能不如使用
weakref
好,但是它是一个选项,该选项对于仅使用python内置函数的任何动态分配函数都可以透明地工作。 如果您发现自己经常这样做,则可能需要一个自定义元类,它可以对实例级功能进行动态绑定。 另一种选择是将函数直接添加到类中,该类将在调用时正确执行绑定。在很多用例中,这将涉及一些麻烦:即,正确命名函数的空间,以免它们冲突。不过,实例ID可以用于此目的,因为不能保证cPython中的ID在程序的整个生命周期内都是唯一的,因此您需要仔细考虑一下以确保它适用于您的用例。特别是,当对象超出范围时,您可能需要确保删除类函数,因此其ID /内存地址可以再次使用。
__del__
非常适合:)。另外,您可以清除对象创建时命名为实例的所有方法(在
__init__
__new__
中)。 另一个选择(而不是弄乱python magic方法)是显式添加一种用于调用动态绑定函数的方法。缺点是您的用户无法使用常规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,type(self))(*args,**kwargs)

    \"\"\"
    Alternate without using descriptor functionality:
    def call_dynamic_func(self,func_name)(self,**kwargs)
    \"\"\"
为了使这篇文章更完整,我还将展示您的
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))