Author: Armin Rigo <[email protected]>
Branch: py3.5
Changeset: r89476:3e79e13a6a69
Date: 2017-01-10 17:53 +0100
http://bitbucket.org/pypy/pypy/changeset/3e79e13a6a69/
Log: Tweak: W_IOBase is the only class in CPython 3.5 to have both a
tp_finalize and be overridable. This gives semantics that are
markedly different from the old (<= 3.3) ones, and that are closer
to app-level classes. See comments
diff --git a/pypy/interpreter/typedef.py b/pypy/interpreter/typedef.py
--- a/pypy/interpreter/typedef.py
+++ b/pypy/interpreter/typedef.py
@@ -13,7 +13,7 @@
class TypeDef(object):
def __init__(self, __name, __base=None, __total_ordering__=None,
- __buffer=None, **rawdict):
+ __buffer=None, __confirm_applevel_del__=False, **rawdict):
"NOT_RPYTHON: initialization-time only"
self.name = __name
if __base is None:
@@ -29,7 +29,8 @@
self.heaptype = False
self.hasdict = '__dict__' in rawdict
# no __del__: use an RPython _finalize_() method and register_finalizer
- assert '__del__' not in rawdict
+ if not __confirm_applevel_del__:
+ assert '__del__' not in rawdict
self.weakrefable = '__weakref__' in rawdict
self.doc = rawdict.get('__doc__', None)
for base in bases:
diff --git a/pypy/module/_io/interp_bufferedio.py
b/pypy/module/_io/interp_bufferedio.py
--- a/pypy/module/_io/interp_bufferedio.py
+++ b/pypy/module/_io/interp_bufferedio.py
@@ -1010,16 +1010,6 @@
self.w_writer = None
raise
- def _finalize_(self):
- # Don't call the base __del__: do not close the files!
- # Usually the _finalize_() method is not called at all because
- # we set 'needs_to_finalize = False' in this class, so
- # W_IOBase.__init__() won't call register_finalizer().
- # However, this method might still be called: if the user
- # makes an app-level subclass and adds a custom __del__.
- pass
- needs_to_finalize = False
-
# forward to reader
for method in ['read', 'peek', 'read1', 'readinto', 'readable']:
locals()[method + '_w'] = make_forwarding_method(
@@ -1052,6 +1042,10 @@
if e:
raise e
+ def needs_finalizer(self):
+ # self.w_writer and self.w_reader have their own finalizer
+ return type(self) is not W_BufferedRWPair
+
def isatty_w(self, space):
if space.is_true(space.call_method(self.w_writer, "isatty")):
return space.w_True
diff --git a/pypy/module/_io/interp_bytesio.py
b/pypy/module/_io/interp_bytesio.py
--- a/pypy/module/_io/interp_bytesio.py
+++ b/pypy/module/_io/interp_bytesio.py
@@ -166,6 +166,10 @@
def close_w(self, space):
self.close()
+ def needs_finalizer(self):
+ # self.close() is not necessary when the object goes away
+ return type(self) is not W_BytesIO
+
def closed_get_w(self, space):
return space.wrap(self.is_closed())
diff --git a/pypy/module/_io/interp_iobase.py b/pypy/module/_io/interp_iobase.py
--- a/pypy/module/_io/interp_iobase.py
+++ b/pypy/module/_io/interp_iobase.py
@@ -60,7 +60,7 @@
self.__IOBase_closed = False
if add_to_autoflusher:
get_autoflusher(space).add(self)
- if self.needs_to_finalize:
+ if self.needs_finalizer():
self.register_finalizer(space)
def getdict(self, space):
@@ -75,6 +75,18 @@
return False
def _finalize_(self):
+ # Note: there is only this empty _finalize_() method here, but
+ # we still need register_finalizer() so that descr_del() is
+ # called. IMPORTANT: this is not the recommended way to have a
+ # finalizer! It makes the finalizer appear as __del__() from
+ # app-level, and the user can call __del__() explicitly, or
+ # override it, with or without calling the parent's __del__().
+ # This matches 'tp_finalize' in CPython >= 3.4. So far (3.5),
+ # this is the only built-in class with a 'tp_finalize' slot that
+ # can be subclassed.
+ pass
+
+ def descr_del(self):
space = self.space
w_closed = space.findattr(self, space.wrap('closed'))
try:
@@ -90,7 +102,6 @@
# equally as bad, and potentially more frequent (because of
# shutdown issues).
pass
- needs_to_finalize = True
def _CLOSED(self):
# Use this macro whenever you want to check the internal `closed`
@@ -128,6 +139,11 @@
finally:
self.__IOBase_closed = True
+ def needs_finalizer(self):
+ # can return False if we know that the precise close() method
+ # of this class will have no effect
+ return True
+
def _dealloc_warn_w(self, space, w_source):
"""Called when the io is implicitly closed via the deconstructor"""
pass
@@ -318,6 +334,8 @@
doc="True if the file is closed"),
__dict__ = GetSetProperty(descr_get_dict, descr_set_dict, cls=W_IOBase),
__weakref__ = make_weakref_descr(W_IOBase),
+ __del__ = interp2app(W_IOBase.descr_del),
+ __confirm_applevel_del__ = True,
readline = interp2app(W_IOBase.readline_w),
readlines = interp2app(W_IOBase.readlines_w),
diff --git a/pypy/module/_io/interp_stringio.py
b/pypy/module/_io/interp_stringio.py
--- a/pypy/module/_io/interp_stringio.py
+++ b/pypy/module/_io/interp_stringio.py
@@ -248,6 +248,10 @@
def close_w(self, space):
self.buf = None
+ def needs_finalizer(self):
+ # 'self.buf = None' is not necessary when the object goes away
+ return type(self) is not W_StringIO
+
def closed_get_w(self, space):
return space.wrap(self.buf is None)
diff --git a/pypy/module/_io/test/test_bufferedio.py
b/pypy/module/_io/test/test_bufferedio.py
--- a/pypy/module/_io/test/test_bufferedio.py
+++ b/pypy/module/_io/test/test_bufferedio.py
@@ -337,13 +337,33 @@
b.flush()
assert self.readfile() == b'x' * 40
- def test_destructor(self):
+ def test_destructor_1(self):
import _io
record = []
class MyIO(_io.BufferedWriter):
def __del__(self):
record.append(1)
+ # doesn't call the inherited __del__, so file not closed
+ def close(self):
+ record.append(2)
+ super(MyIO, self).close()
+ def flush(self):
+ record.append(3)
+ super(MyIO, self).flush()
+ raw = _io.FileIO(self.tmpfile, 'w')
+ MyIO(raw)
+ import gc; gc.collect()
+ assert record == [1]
+
+ def test_destructor_2(self):
+ import _io
+
+ record = []
+ class MyIO(_io.BufferedWriter):
+ def __del__(self):
+ record.append(1)
+ super(MyIO, self).__del__()
def close(self):
record.append(2)
super(MyIO, self).close()
diff --git a/pypy/module/_io/test/test_io.py b/pypy/module/_io/test/test_io.py
--- a/pypy/module/_io/test/test_io.py
+++ b/pypy/module/_io/test/test_io.py
@@ -80,7 +80,7 @@
bufio.flush()
assert f.getvalue() == b"ABC"
- def test_destructor(self):
+ def test_destructor_1(self):
import io
io.IOBase()
@@ -88,6 +88,26 @@
class MyIO(io.IOBase):
def __del__(self):
record.append(1)
+ # doesn't call the inherited __del__, so file not closed
+ def close(self):
+ record.append(2)
+ super(MyIO, self).close()
+ def flush(self):
+ record.append(3)
+ super(MyIO, self).flush()
+ MyIO()
+ import gc; gc.collect()
+ assert record == [1]
+
+ def test_destructor_2(self):
+ import io
+ io.IOBase()
+
+ record = []
+ class MyIO(io.IOBase):
+ def __del__(self):
+ record.append(1)
+ super(MyIO, self).__del__()
def close(self):
record.append(2)
super(MyIO, self).close()
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit