Author: Armin Rigo <ar...@tunes.org>
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
pypy-commit@python.org
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to