阻止TextIOWrapper以Py2/Py3兼容的方式关闭GC

2024-05-12 21:49:38 发布

您现在位置:Python中文网/ 问答频道 /正文

我需要完成的任务:

给定一个二进制文件,用两种不同的方式解码,提供一个TextIOBaseAPI。理想情况下,这些后续文件可以被传递,而不需要显式地跟踪它们的寿命。在

不幸的是,包装BufferedReader将 导致当TextIOWrapper超出范围时,该读卡器被关闭。在

下面是一个简单的演示:

In [1]: import io

In [2]: def mangle(x):
   ...:     io.TextIOWrapper(x) # Will get GCed causing __del__ to call close
   ...:     

In [3]: f = io.open('example', mode='rb')

In [4]: f.closed
Out[4]: False

In [5]: mangle(f)

In [6]: f.closed
Out[6]: True

我可以在python3中通过重写__del__来解决这个问题(对于我的用例来说这是一个合理的解决方案,因为我可以完全控制解码过程,我只需要在最后公开一个非常统一的API):

^{pr2}$

但是,这在Python 2中不起作用:

In [7]: class MyTextIOWrapper(io.TextIOWrapper):
   ...:     def __del__(self):
   ...:         print("I've been GC'ed")
   ...:         

In [8]: def mangle2(x):
   ...:     MyTextIOWrapper(x)
   ...:     

In [9]: f2 = io.open('example', mode='rb')

In [10]: f2.closed
Out[10]: False

In [11]: mangle2(f2)
I've been GC'ed

In [12]: f2.closed
Out[12]: True

我花了一点时间看Python源代码,它在2.7和3.4之间看起来非常相似,所以我不明白为什么从IOBase继承的__del__在python2中不可重写(甚至在dir中可见),但似乎仍然可以执行。Python3与预期完全一致。在

我能做些什么吗?在


Tags: 文件iniomodeexampledefopenout
3条回答

编辑:

Just call ^{} first, thanks martijn-pieters!


事实证明,对于Python2.7中调用close的解构器,基本上什么也做不了。这是硬编码到C代码中的。相反,我们可以修改close,这样当__del__发生时它不会关闭缓冲区(在C代码中,__del__将在_PyIOBase_finalize之前执行,给我们一个改变close行为的机会)。这使close按预期工作,而不让GC关闭缓冲区。在

class SaneTextIOWrapper(io.TextIOWrapper):
    def __init__(self, *args, **kwargs):
        self._should_close_buffer = True
        super(SaneTextIOWrapper, self).__init__(*args, **kwargs)

    def __del__(self):
        # Accept the inevitability of the buffer being closed by the destructor
        # because of this line in Python 2.7:
        # https://github.com/python/cpython/blob/2.7/Modules/_io/iobase.c#L221
        self._should_close_buffer = False
        self.close()  # Actually close for Python 3 because it is an override.
                      # We can't call super because Python 2 doesn't actually
                      # have a `__del__` method for IOBase (hence this
                      # workaround). Close is idempotent so it won't matter
                      # that Python 2 will end up calling this twice

    def close(self):
        # We can't stop Python 2.7 from calling close in the deconstructor
        # so instead we can prevent the buffer from being closed with a flag.

        # Based on:
        # https://github.com/python/cpython/blob/2.7/Lib/_pyio.py#L1586
        # https://github.com/python/cpython/blob/3.4/Lib/_pyio.py#L1615
        if self.buffer is not None and not self.closed:
            try:
                self.flush()
            finally:
                if self._should_close_buffer:
                    self.buffer.close()

我之前的解决方案使用了_pyio.TextIOWrapper,这比上面的要慢,因为它是用Python编写的,而不是用C编写的

它只需要用一个noop覆盖{},这个noop也可以在Py2/3中工作。在

一个简单的解决方案是从函数返回变量并将其存储在脚本范围内,这样在脚本结束或对它的引用发生更改之前,它不会被垃圾回收。但可能还有其他优雅的解决方案。在

只需将TextIOWrapper()对象分离,然后将其垃圾回收:

def mangle(x):
    wrapper = io.TextIOWrapper(x)
    wrapper.detach()

TextIOWrapper()对象只关闭它所附加到的流。如果不能更改对象超出范围的代码,那么只需在本地保留对TextIOWrapper()对象的引用并在该点分离。在

如果必须子类TextIOWrapper(),那么只要在__del__钩子中调用detach()

^{pr2}$

相关问题 更多 >