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

ebo*_*yen 8 python garbage-collection python-2.7 python-3.x

我需要完成的事情:

给定二进制文件,以提供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
Run Code Online (Sandbox Code Playgroud)

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

In [1]: import io

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

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

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

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

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

In [7]: f2.closed
Out[7]: False
Run Code Online (Sandbox Code Playgroud)

但是这在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
Run Code Online (Sandbox Code Playgroud)

我花了一些时间盯着Python源代码,它在2.7和3.4之间看起来非常相似所以我不明白为什么__del__继承IOBase自在Python 2中不可覆盖(甚至可见dir),但似乎仍然得到执行.Python 3完全按预期工作.

有什么我能做的吗?

Mar*_*ers 6

只需取下你的TextIOWrapper()对象让它被垃圾收集之前:

def mangle(x):
    wrapper = io.TextIOWrapper(x)
    wrapper.detach()
Run Code Online (Sandbox Code Playgroud)

TextIOWrapper()对象仅关闭它所连接的流。如果您无法更改对象超出范围的代码,则只需在本地保留对该TextIOWrapper()对象的引用并在该点分离。

如果你必须继承TextIOWrapper(),那么只需要调用detach()__del__钩:

class DetachingTextIOWrapper(io.TextIOWrapper):
    def __del__(self):
        self.detach()
Run Code Online (Sandbox Code Playgroud)