[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