43

有人可以解释为什么以下代码的行为方式如下:

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__. 我试图创建最短的函数来展示我认为是非直觉的行为。我假设已经创建了一个循环引用,但我不确定为什么。如果可能的话,我想知道如何避免循环引用....

4

8 回答 8

40

你不能假设它__del__会被调用——它不是一个希望资源被自动释放的地方。如果您想确保释放(非内存)资源,您应该创建一个release()或类似的方法,然后显式调用它(或在上下文管理器中使用它,正如 Thanatos 在下面的评论中指出的那样)。

至少您应该非常仔细地阅读__del__ 文档,然后您可能不应该尝试使用__del__. (有关其他不好的事情,请参阅gc.garbage 文档__del__

于 2011-05-24T00:31:45.173 回答
23

我提供自己的答案是因为,虽然我很欣赏避免使用的建议__del__,但我的问题是如何让它为所提供的代码示例正常工作。

短版:以下代码用于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"

更长的版本:当我发布问题时,我确实搜索了类似的问题。我知道您可以with改用,并且普遍的看法__del__BAD

使用with是有意义的,但仅在某些情况下。打开一个文件,阅读它,然后关闭它是一个很好的例子,这with是一个很好的解决方案。您已经完成了需要对象的特定代码块,并且想要清理对象和块的末尾。

数据库连接似乎经常被用作不能很好地使用的示例with,因为您通常需要保留创建连接的代码部分并在更受事件驱动(而不是顺序)的时间范围内关闭连接.

如果with不是正确的解决方案,我会看到两种选择:

  1. 您确保有效(有关弱引用用法的更好描述,__del__请参阅此博客)
  2. 当您的程序关闭时,您可以使用该atexit模块运行回调。例如,请参阅本主题

虽然我试图提供简化代码,但我真正的问题更多是事件驱动的,所以with不是一个合适的解决方案(with对于简化代码来说很好)。我也想避免atexit,因为我的程序可能会长时间运行,并且我希望能够尽快执行清理。

因此,在这种特定情况下,我发现它是使用weakref和防止会阻止__del__工作的循环引用的最佳解决方案。

这可能是该规则的一个例外,但是在某些用例中使用weakrefand__del__是正确的实现,恕我直言。

于 2011-05-29T02:04:09.450 回答
11

代替del,您可以使用with运算符。

http://effbot.org/zone/python-with-statement.htm

就像文件类型对象一样,你可以像

with Dummy('d1') as d:
    #stuff
#d's __exit__ method is guaranteed to have been called
于 2011-05-24T00:37:48.723 回答
8

del不打电话__del__

del以您使用的方式删除局部变量。__del__当对象被销毁时调用。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

这保证有效,您甚至可以检查参数以查看您是否正在处理异常并在这种情况下执行不同的操作。

于 2011-05-24T00:43:26.497 回答
2

上下文管理器的完整示例。

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
于 2011-05-29T16:52:19.233 回答
2

在我看来,问题的真正核心在这里:

添加函数是动态的(在运行时)并且事先不知道

我感觉到您真正追求的是一种灵活的方法,可以将不同的功能绑定到表示程序状态的对象,也称为多态性。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()
于 2013-06-12T20:39:25.090 回答
0

使用的另一种解决方案是仅在通过覆盖或在要返回的类上weakref调用函数时才将函数动态绑定到实例,而不仅仅是绑定到实例的函数。这就是类上定义的函数的行为方式。不幸的是(对于某些用例)python 不会对附加到实例本身的函数执行相同的逻辑,但您可以修改它来执行此操作。我在绑定到实例的描述符方面遇到了类似的问题。这里的性能可能不如 using ,但它是一个选项,它可以透明地用于仅使用 python 内置函数的任何动态分配的函数。__getattr____getattribute__func.__get__(self, type(self))funcweakref

如果您发现自己经常这样做,您可能需要一个自定义元类来动态绑定实例级函数。

另一种选择是将函数直接添加到类中,然后在调用它时正确执行绑定。对于很多用例,这会涉及到一些令人头疼的问题:即,正确地命名函数以使它们不会发生冲突。但是,实例 id 可以用于此目的,因为 cPython 中的 id 不能保证在程序的整个生命周期内都是唯一的,因此您需要考虑一下以确保它适用于您的用例......特别是,您可能需要确保在对象超出范围时删除类函数,因此其 id/内存地址再次可用。__del__非常适合这个:)。或者,您可以在创建对象时清除所有命名为实例的方法(在__init__或中__new__)。

另一种选择(而不是使用 python 魔术方法)是显式添加一个用于调用动态绑定函数的方法。这样做的缺点是您的用户无法使用普通的 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)
    """

为了使这篇文章完整,我也会展示你的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))
于 2017-09-23T15:41:42.753 回答
0

使用 eval()


In [1]: int('25.0')                                                                                                                                                                                                                                                               
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-1-67d52e3d0c17> in <module>
----> 1 int('25.0')

ValueError: invalid literal for int() with base 10: '25.0'

In [2]: int(float('25.0'))                                                                                                                                                                                                                                                        
Out[2]: 25

In [3]: eval('25.0')                                                                                                                                                                                                                                                              
Out[3]: 25.0
于 2021-09-01T10:25:18.297 回答