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