__del__中以Pythonic方式关闭连接类对象

5 投票
6 回答
7901 浏览
提问于 2025-04-18 12:20

我正在做一个类似连接的对象,它实现了上下文管理器。像这样写是非常推荐的:

with MyConnection() as con:
    # do stuff

当然,也可以这样做:

con = MyConnection()
# do stuff
con.close()

但是,如果不关闭连接,那就会出现问题。所以在__del__()里关闭连接似乎是个好主意:

def __del__(self):
    self.close()

这样看起来不错,但有时候会导致错误:

Exception ignored in: [...]
Traceback (most recent call last):
  File "...", line xxx, in __del__()
TypeError: 'NoneType' object is not callable

看起来有时候在调用__del__()时,关闭方法已经被销毁了。

所以我在寻找一种好的方法,来确保在对象被销毁时,Python能正确地关闭连接。如果可以的话,我希望在close()__del__()中避免代码重复。

6 个回答

1

weakref.finalize() 这个功能可以让你在一个对象被垃圾回收的时候,或者程序退出时,执行一些操作。从 Python 3.4 开始就可以使用这个功能。

当你创建一个对象时,可以调用 finalize(),并给它传入一个回调函数,这个函数可以用来清理你对象所占用的资源。

Python 的文档中提供了几个使用的例子

>>> import weakref
>>> class Object:
...     pass
...
>>> kenny = Object()
>>> weakref.finalize(kenny, print, "You killed Kenny!")  
<finalize object at ...; for 'Object' at ...>
>>> del kenny
You killed Kenny!

还有这个例子,展示了一个表示临时目录的类,当以下情况发生时,它的内容会被删除:

  • 调用它的 remove() 方法
  • 被垃圾回收
  • 程序退出

无论哪种情况先发生。

class TempDir:
    def __init__(self):
        self.name = tempfile.mkdtemp()
        self._finalizer = weakref.finalize(self, shutil.rmtree, self.name)

    def remove(self):
        self._finalizer()

    @property
    def removed(self):
        return not self._finalizer.alive
1

确实,你无法强迫用户使用好的编程技巧,如果他们不愿意这样做,你也不能为他们负责。

关于__del__这个方法什么时候会被调用,没有固定的时间保证——在某些Python版本中,它会立刻被调用,而在其他版本中,可能要等到解释器关闭时才会调用。所以,虽然这不是一个很好的选择,但使用atexit这个模块可能是可行的——只要确保你注册的函数足够聪明,能够检查资源是否已经被关闭或销毁。

1

你可以试着在 __del__ 里面调用 close,然后忽略任何可能出现的错误:

del __del__(self):
    try:
        self.close()
    except TypeError:
        pass
3

没有办法保证 __del__ 什么时候会真正执行。因为你在使用 with 语句,所以应该用 __exit__ 方法。__exit__ 会在 with 语句结束后立刻被调用,不管这个语句是正常结束的,还是因为出现了错误等情况。

12

如果你真的想防止用户不关闭连接,可以只在 __enter__ 这个地方初始化连接,或者你可以加一个标志,来检查连接是否已经被上下文管理器初始化过。比如,可以这样做:

class MyConnection(object):

    safely_initialized = False

    def __enter__(self):
        # Init your connection
        self.safely_initialized = True
        return self

    def do_something(self):
        if not self.safely_initialized:
            raise Exception('You must initialize the connection with a context manager!')
        # Do something

    def __exit__(self, type, value, traceback):
        # Close your connection

这样的话,连接就不会被初始化,除非是在上下文管理器里面。

撰写回答