Author: Armin Rigo <[email protected]>
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)
_______________________________________________
pypy-commit mailing list
[email protected]
http://mail.python.org/mailman/listinfo/pypy-commit