[pypy-svn] r53780 - in pypy/dist/pypy/interpreter: . test
arigo at codespeak.net
arigo at codespeak.net
Tue Apr 15 13:44:47 CEST 2008
Author: arigo
Date: Tue Apr 15 13:44:47 2008
New Revision: 53780
Modified:
pypy/dist/pypy/interpreter/test/test_typedef.py
pypy/dist/pypy/interpreter/typedef.py
Log:
Refactor the logic that creates interp-level subclasses of the
W_XxxObject classes. It was broken because it created much more
combinations than the 5 that it eventually used.
Another issue was with creating a class with __slots__=[] and with a
destructor. The instance could contain arbitrary attributes, which is
probably wrong (and definitely unexpected).
Modified: pypy/dist/pypy/interpreter/test/test_typedef.py
==============================================================================
--- pypy/dist/pypy/interpreter/test/test_typedef.py (original)
+++ pypy/dist/pypy/interpreter/test/test_typedef.py Tue Apr 15 13:44:47 2008
@@ -1,3 +1,4 @@
+from pypy.interpreter import typedef
# this test isn't so much to test that the objspace interface *works*
# -- it's more to test that it's *there*
@@ -57,3 +58,75 @@
class A(object):
pass
assert A.__dict__['__dict__'].__name__ == '__dict__'
+
+
+class TestTypeDef:
+
+ def test_subclass_cache(self):
+ # check that we don't create more than 6 subclasses of a
+ # given W_XxxObject (instead of the 16 that follow from
+ # all combinations)
+ space = self.space
+ sources = []
+ for hasdict in [False, True]:
+ for wants_slots in [False, True]:
+ for needsdel in [False, True]:
+ for weakrefable in [False, True]:
+ print 'Testing case', hasdict, wants_slots,
+ print needsdel, weakrefable
+ slots = []
+ checks = []
+
+ if hasdict:
+ slots.append('__dict__')
+ checks.append('x.foo=5; x.__dict__')
+ else:
+ checks.append('raises(AttributeError, "x.foo=5");'
+ 'raises(AttributeError, "x.__dict__")')
+
+ if wants_slots:
+ slots.append('a')
+ checks.append('x.a=5; assert X.a.__get__(x)==5')
+ else:
+ checks.append('')
+
+ if weakrefable:
+ slots.append('__weakref__')
+ checks.append('import _weakref;_weakref.ref(x)')
+ else:
+ checks.append('')
+
+ if needsdel:
+ methodname = '__del__'
+ checks.append('X();X();X();'
+ 'import gc;gc.collect();'
+ 'assert seen')
+ else:
+ methodname = 'spam'
+ checks.append('assert "Del" not in irepr')
+
+ assert len(checks) == 4
+ space.appexec([], """():
+ seen = []
+ class X(list):
+ __slots__ = %r
+ def %s(self):
+ seen.append(1)
+ x = X()
+ import __pypy__
+ irepr = __pypy__.internal_repr(x)
+ print irepr
+ %s
+ %s
+ %s
+ %s
+ """ % (slots, methodname, checks[0], checks[1],
+ checks[2], checks[3]))
+ subclasses = {}
+ for key, subcls in typedef._subclass_cache.items():
+ cls = key[0]
+ subclasses.setdefault(cls, {})
+ subclasses[cls][subcls] = True
+ for cls, set in subclasses.items():
+ assert len(set) <= 6, "%s has %d subclasses:\n%r" % (
+ cls, len(set), [subcls.__name__ for subcls in set])
Modified: pypy/dist/pypy/interpreter/typedef.py
==============================================================================
--- pypy/dist/pypy/interpreter/typedef.py (original)
+++ pypy/dist/pypy/interpreter/typedef.py Tue Apr 15 13:44:47 2008
@@ -91,62 +91,148 @@
no_hash_descr = interp2app(descr__hash__unhashable)
# ____________________________________________________________
+#
+# For each built-in app-level type Xxx that can be subclassed at
+# app-level, the corresponding interp-level W_XxxObject class cannot
+# generally represent instances of app-level subclasses of Xxx. The
+# reason is that it is missing a place to store the __dict__, the slots,
+# the weakref lifeline, and it typically has no interp-level __del__.
+# So we create a few interp-level subclasses of W_XxxObject, which add
+# some combination of features.
+#
+# We don't build 2**4 == 16 subclasses for all combinations of requested
+# features, but limit ourselves to 6, chosen a bit arbitrarily based on
+# typical usage (case 1 is the most common kind of app-level subclasses;
+# case 2 is the memory-saving kind defined with __slots__).
+#
+# dict slots del weakrefable
+#
+# 1. Y N N Y UserDictWeakref
+# 2. N Y N N UserSlots
+# 3. Y Y N Y UserDictWeakrefSlots
+# 4. N Y N Y UserSlotsWeakref
+# 5. Y Y Y Y UserDictWeakrefSlotsDel
+# 6. N Y Y Y UserSlotsWeakrefDel
+#
+# Note that if the app-level explicitly requests no dict, we should not
+# provide one, otherwise storing random attributes on the app-level
+# instance would unexpectedly work. We don't care too much, though, if
+# an object is weakrefable when it shouldn't really be. It's important
+# that it has a __del__ only if absolutely needed, as this kills the
+# performance of the GCs.
+#
+# Interp-level inheritance is like this:
+#
+# W_XxxObject base
+# / \
+# 1 2
+# / \
+# 3 4
+# / \
+# 5 6
+
def get_unique_interplevel_subclass(cls, hasdict, wants_slots, needsdel=False,
weakrefable=False):
- if needsdel:
- hasdict = wants_slots = weakrefable = True
- if hasdict:
- weakrefable = True
- else:
- wants_slots = True
- return _get_unique_interplevel_subclass(cls, hasdict, wants_slots, needsdel, weakrefable)
-get_unique_interplevel_subclass._annspecialcase_ = "specialize:memo"
-
-def _get_unique_interplevel_subclass(cls, hasdict, wants_slots, needsdel, weakrefable):
"NOT_RPYTHON: initialization-time only"
- typedef = cls.typedef
- if hasdict and typedef.hasdict:
- hasdict = False
- if weakrefable and typedef.weakrefable:
- weakrefable = False
-
key = cls, hasdict, wants_slots, needsdel, weakrefable
try:
return _subclass_cache[key]
except KeyError:
- subcls = _buildusercls(cls, hasdict, wants_slots, needsdel, weakrefable)
+ subcls = _getusercls(cls, hasdict, wants_slots, needsdel, weakrefable)
_subclass_cache[key] = subcls
return subcls
+get_unique_interplevel_subclass._annspecialcase_ = "specialize:memo"
_subclass_cache = {}
-def _buildusercls(cls, hasdict, wants_slots, wants_del, weakrefable):
- "NOT_RPYTHON: initialization-time only"
- name = ['User']
- if not hasdict:
- name.append('NoDict')
- if wants_slots:
- name.append('WithSlots')
+def _getusercls(cls, wants_dict, wants_slots, wants_del, weakrefable):
+ typedef = cls.typedef
+ if wants_dict and typedef.hasdict:
+ wants_dict = False
+ # Forest of if's - see the comment above.
if wants_del:
- name.append('WithDel')
- if weakrefable:
- name.append('Weakrefable')
-
- name.append(cls.__name__)
-
- name = ''.join(name)
- if weakrefable:
- supercls = _get_unique_interplevel_subclass(cls, hasdict, wants_slots,
- wants_del, False)
+ if wants_dict:
+ # case 5. Parent class is 3.
+ parentcls = get_unique_interplevel_subclass(cls, True, True,
+ False, True)
+ else:
+ # case 6. Parent class is 4.
+ parentcls = get_unique_interplevel_subclass(cls, False, True,
+ False, True)
+ return _usersubclswithfeature(parentcls, "del")
+ elif wants_dict:
+ if wants_slots:
+ # case 3. Parent class is 1.
+ parentcls = get_unique_interplevel_subclass(cls, True, False,
+ False, True)
+ return _usersubclswithfeature(parentcls, "slots")
+ else:
+ # case 1 (we need to add weakrefable unless it's already in 'cls')
+ if not typedef.weakrefable:
+ return _usersubclswithfeature(cls, "user", "dict", "weakref")
+ else:
+ return _usersubclswithfeature(cls, "user", "dict")
+ else:
+ if weakrefable and not typedef.weakrefable:
+ # case 4. Parent class is 2.
+ parentcls = get_unique_interplevel_subclass(cls, False, True,
+ False, False)
+ return _usersubclswithfeature(parentcls, "weakref")
+ else:
+ # case 2 (if the base is already weakrefable, case 2 == case 4)
+ return _usersubclswithfeature(cls, "user", "slots")
+
+def _usersubclswithfeature(parentcls, *features):
+ key = parentcls, features
+ try:
+ return _usersubclswithfeature_cache[key]
+ except KeyError:
+ subcls = _builduserclswithfeature(parentcls, *features)
+ _usersubclswithfeature_cache[key] = subcls
+ return subcls
+_usersubclswithfeature_cache = {}
+_allusersubcls_cache = {}
+
+def _builduserclswithfeature(supercls, *features):
+ "NOT_RPYTHON: initialization-time only"
+ name = supercls.__name__
+ name += ''.join([name.capitalize() for name in features])
+ body = {}
+ #print '..........', name, '(', supercls.__name__, ')'
+
+ def add(Proto):
+ for key, value in Proto.__dict__.items():
+ if not key.startswith('__') or key == '__del__':
+ body[key] = value
+
+ if "user" in features: # generic feature needed by all subcls
+ class Proto(object):
+ def getclass(self, space):
+ return self.w__class__
+
+ def setclass(self, space, w_subtype):
+ # only used by descr_set___class__
+ self.w__class__ = w_subtype
+
+ def user_setup(self, space, w_subtype):
+ self.space = space
+ self.w__class__ = w_subtype
+ self.user_setup_slots(w_subtype.nslots)
+
+ def user_setup_slots(self, nslots):
+ assert nslots == 0
+ add(Proto)
+
+ if "weakref" in features:
class Proto(object):
_lifeline_ = None
def getweakref(self):
return self._lifeline_
def setweakref(self, space, weakreflifeline):
self._lifeline_ = weakreflifeline
- elif wants_del:
- supercls = _get_unique_interplevel_subclass(cls, hasdict, wants_slots,
- False, False)
- parent_destructor = getattr(cls, '__del__', None)
+ add(Proto)
+
+ if "del" in features:
+ parent_destructor = getattr(supercls, '__del__', None)
class Proto(object):
def __del__(self):
self.clear_all_weakrefs()
@@ -157,23 +243,21 @@
e.clear(self.space) # break up reference cycles
if parent_destructor is not None:
parent_destructor(self)
- elif wants_slots:
- supercls = _get_unique_interplevel_subclass(cls, hasdict, False, False, False)
-
+ add(Proto)
+
+ if "slots" in features:
class Proto(object):
slots_w = []
def user_setup_slots(self, nslots):
if nslots > 0:
self.slots_w = [None] * nslots
-
def setslotvalue(self, index, w_value):
self.slots_w[index] = w_value
-
def getslotvalue(self, index):
return self.slots_w[index]
- elif hasdict:
- supercls = _get_unique_interplevel_subclass(cls, False, False, False, False)
-
+ add(Proto)
+
+ if "dict" in features:
class Proto(object):
def getdict(self):
return self.w__dict__
@@ -216,33 +300,11 @@
if not w_dict.implementation.shadows_anything():
return None
return space.finditem(w_dict, w_name)
-
- else:
- supercls = cls
-
- class Proto(object):
-
- def getclass(self, space):
- return self.w__class__
-
- def setclass(self, space, w_subtype):
- # only used by descr_set___class__
- self.w__class__ = w_subtype
-
-
- def user_setup(self, space, w_subtype):
- self.space = space
- self.w__class__ = w_subtype
- self.user_setup_slots(w_subtype.nslots)
-
- def user_setup_slots(self, nslots):
- assert nslots == 0
-
- body = dict([(key, value)
- for key, value in Proto.__dict__.items()
- if not key.startswith('__') or key == '__del__'])
+ add(Proto)
+
subcls = type(name, (supercls,), body)
+ _allusersubcls_cache[subcls] = True
return subcls
def make_descr_typecheck_wrapper(func, extraargs=(), cls=None):
More information about the Pypy-commit
mailing list