[pypy-commit] pypy refactor-wrapped-del: Refactor the creation of interp-level __del__ methods,

arigo noreply at buildbot.pypy.org
Mon Jul 11 19:17:02 CEST 2011


Author: Armin Rigo <arigo at tunes.org>
Branch: refactor-wrapped-del
Changeset: r45469:1605b4011b4d
Date: 2011-07-11 14:02 +0200
http://bitbucket.org/pypy/pypy/changeset/1605b4011b4d/

Log:	Refactor the creation of interp-level __del__ methods, with a nice
	interface now.

diff --git a/pypy/interpreter/baseobjspace.py b/pypy/interpreter/baseobjspace.py
--- a/pypy/interpreter/baseobjspace.py
+++ b/pypy/interpreter/baseobjspace.py
@@ -165,7 +165,7 @@
             space.user_del_action.register_dying_object(self)
 
     def _call_builtin_destructor(self):
-        pass     # method overridden in typedef.py
+        pass     # method overridden by builtin_destructor() in typedef.py
 
     # hooks that the mapdict implementations needs:
     def _get_mapdict_map(self):
diff --git a/pypy/interpreter/test/test_typedef.py b/pypy/interpreter/test/test_typedef.py
--- a/pypy/interpreter/test/test_typedef.py
+++ b/pypy/interpreter/test/test_typedef.py
@@ -1,3 +1,4 @@
+import gc
 from pypy.interpreter import typedef
 from pypy.tool.udir import udir
 from pypy.interpreter.baseobjspace import Wrappable
@@ -180,6 +181,80 @@
             assert err.value.message == "'some_type' objects are unhashable"
             """)
 
+    def test_destructor(self):
+        space = self.space
+        class W_Level1(Wrappable):
+            space = self.space
+            def __del__(self):
+                space.call_method(w_seen, 'append', space.wrap(1))
+        class W_Level2(W_Level1):
+            typedef.builtin_destructor(locals(), 'destructormeth', W_Level1)
+            def destructormeth(self):
+                space.call_method(w_seen, 'append', space.wrap(2))
+        W_Level1.typedef = typedef.TypeDef(
+            'level1',
+            __new__ = typedef.generic_new_descr(W_Level1))
+        W_Level2.typedef = typedef.TypeDef(
+            'level2',
+            __new__ = typedef.generic_new_descr(W_Level2))
+        #
+        w_seen = space.newlist([])
+        W_Level1()
+        gc.collect(); gc.collect()
+        assert space.str_w(space.repr(w_seen)) == "[1]"
+        #
+        w_seen = space.newlist([])
+        W_Level2()
+        gc.collect(); gc.collect()
+        assert space.str_w(space.repr(w_seen)) == "[]"  # not called yet
+        ec = space.getexecutioncontext()
+        self.space.user_del_action.perform(ec, None)
+        assert space.str_w(space.repr(w_seen)) == "[2, 1]"
+        #
+        w_seen = space.newlist([])
+        self.space.appexec([self.space.gettypeobject(W_Level1.typedef)],
+        """(level1):
+            class A3(level1):
+                pass
+            A3()
+        """)
+        gc.collect(); gc.collect()
+        assert space.str_w(space.repr(w_seen)) == "[1]"
+        #
+        w_seen = space.newlist([])
+        self.space.appexec([self.space.gettypeobject(W_Level1.typedef),
+                            w_seen],
+        """(level1, seen):
+            class A4(level1):
+                def __del__(self):
+                    seen.append(4)
+            A4()
+        """)
+        gc.collect(); gc.collect()
+        assert space.str_w(space.repr(w_seen)) == "[4, 1]"
+        #
+        w_seen = space.newlist([])
+        self.space.appexec([self.space.gettypeobject(W_Level2.typedef)],
+        """(level2):
+            class A5(level2):
+                pass
+            A5()
+        """)
+        gc.collect(); gc.collect()
+        assert space.str_w(space.repr(w_seen)) == "[2, 1]"
+        #
+        w_seen = space.newlist([])
+        self.space.appexec([self.space.gettypeobject(W_Level2.typedef),
+                            w_seen],
+        """(level2, seen):
+            class A6(level2):
+                def __del__(self):
+                    seen.append(6)
+            A6()
+        """)
+        gc.collect(); gc.collect()
+        assert space.str_w(space.repr(w_seen)) == "[6, 2, 1]"
+
 
 class AppTestTypeDef:
 
diff --git a/pypy/interpreter/typedef.py b/pypy/interpreter/typedef.py
--- a/pypy/interpreter/typedef.py
+++ b/pypy/interpreter/typedef.py
@@ -232,17 +232,7 @@
 
     if "del" in features:
         class Proto(object):
-            def __del__(self):
-                self._enqueue_for_destruction(self.space)
-        # if the base class needs its own interp-level __del__,
-        # we override the _call_builtin_destructor() method to invoke it
-        # after the app-level destructor.
-        parent_destructor = getattr(supercls, '__del__', None)
-        if parent_destructor is not None:
-            def _call_builtin_destructor(self):
-                parent_destructor(self)
-            Proto._call_builtin_destructor = _call_builtin_destructor
-
+            builtin_destructor(locals(), None, supercls)
         add(Proto)
 
     if "slots" in features:
@@ -294,6 +284,47 @@
     return w_dict
 check_new_dictionary._dont_inline_ = True
 
+
+def builtin_destructor(loc, methodname, baseclass):
+    # Destructor logic:
+    #  * if we have none, then the class has no __del__.
+    #  * if we have a "simple" interp-level one, it is just written as a
+    #    __del__.  ("simple" destructors may not do too much, e.g. they must
+    #    never call further Python code; this is checked at translation-time)
+    #  * if we have a "complex" interp-level destructor, then we define it in
+    #    a method foo(), followed by
+    #        ``builtin_destructor(locals(), "foo", W_Base)''.
+    #  * if we have an app-level destructor, then typedef.py will also
+    #    call builtin_destructor().
+    # In the last two cases, the __del__ just calls _enqueue_for_destruction()
+    # and executioncontext.UserDelAction does the real job.
+
+    assert '_builtin_destructor_already_called_' not in loc
+    assert '__del__' not in loc
+    assert '_call_builtin_destructor' not in loc
+
+    if hasattr(baseclass, '_builtin_destructor_already_called_'):
+        # builtin_destructor() was already called on the parent
+        # class, so we don't need to install the __del__ method nor
+        # call the __del__ method from _call_builtin_destructor()
+        # (because the parent _call_builtin_destructor() will do it)
+        parent_del = None
+    else:
+        def __del__(self):
+            self._enqueue_for_destruction(self.space)
+        loc['__del__'] = __del__
+        loc['_builtin_destructor_already_called_'] = True
+        parent_del = getattr(baseclass, '__del__', None)
+
+    if methodname is not None or parent_del is not None:
+        def _call_builtin_destructor(self):
+            if methodname is not None:
+                getattr(self, methodname)()
+            if parent_del is not None:
+                parent_del(self)
+            baseclass._call_builtin_destructor(self)
+        loc['_call_builtin_destructor'] = _call_builtin_destructor
+
 # ____________________________________________________________
 
 @specialize.arg(0)


More information about the pypy-commit mailing list