[pypy-commit] pypy identity-dict-strategy: implement a global version counter to track changes to classes that changed their compares_by_identity() status

antocuni noreply at buildbot.pypy.org
Tue Jul 19 18:44:21 CEST 2011


Author: Antonio Cuni <anto.cuni at gmail.com>
Branch: identity-dict-strategy
Changeset: r45743:923f2db3961e
Date: 2011-07-19 18:19 +0200
http://bitbucket.org/pypy/pypy/changeset/923f2db3961e/

Log:	implement a global version counter to track changes to classes that
	changed their compares_by_identity() status

diff --git a/pypy/objspace/std/objspace.py b/pypy/objspace/std/objspace.py
--- a/pypy/objspace/std/objspace.py
+++ b/pypy/objspace/std/objspace.py
@@ -32,18 +32,19 @@
 from pypy.objspace.std.smallintobject import W_SmallIntObject
 from pypy.objspace.std.stringobject import W_StringObject
 from pypy.objspace.std.tupleobject import W_TupleObject
-from pypy.objspace.std.typeobject import W_TypeObject
+from pypy.objspace.std.typeobject import W_TypeObject, VersionTag
 
 # types
 from pypy.objspace.std.inttype import wrapint
 from pypy.objspace.std.stringtype import wrapstr
 from pypy.objspace.std.unicodetype import wrapunicode
 
-
 class StdObjSpace(ObjSpace, DescrOperation):
     """The standard object space, implementing a general-purpose object
     library in Restricted Python."""
 
+    compares_by_identity_version = None
+
     def initialize(self):
         "NOT_RPYTHON: only for initializing the space."
         # setup all the object types and implementations
@@ -80,6 +81,15 @@
         # the type of old-style classes
         self.w_classobj = self.builtin.get('__metaclass__')
 
+        # a global version counter to track live instances which "compare by
+        # identity" (i.e., whose __eq__, __cmp__ and __hash__ are the default
+        # ones).  The idea is to track only classes for which we checked the
+        # compares_by_identity() status at least once: we increment the
+        # version if its status might change, e.g. because we set one of those
+        # attributes.  The actual work is done by W_TypeObject.mutated()
+        if self.config.objspace.std.trackcomparebyidentity:
+            self.compares_by_identity_version = VersionTag()
+
         # final setup
         self.setup_builtin_modules()
         # Adding transparent proxy call
diff --git a/pypy/objspace/std/test/test_typeobject.py b/pypy/objspace/std/test/test_typeobject.py
--- a/pypy/objspace/std/test/test_typeobject.py
+++ b/pypy/objspace/std/test/test_typeobject.py
@@ -1209,8 +1209,13 @@
 
         def compares_by_identity(space, w_cls):
             return space.wrap(w_cls.compares_by_identity())
+        cls.w_compares_by_identity = cls.space.wrap(interp2app(compares_by_identity))
 
-        cls.w_compares_by_identity = cls.space.wrap(interp2app(compares_by_identity))
+        versions = {}
+        def get_version(space):
+            v = versions.setdefault(space.compares_by_identity_version, len(versions))
+            return space.wrap(v)
+        cls.w_get_version = cls.space.wrap(interp2app(get_version))
 
     def test_compares_by_identity(self):
         class Plain(object):
@@ -1234,14 +1239,43 @@
         assert not self.compares_by_identity(CustomHash)
 
     def test_modify_class(self):
-        def foo(self, *args):
-            pass
-
         class X(object):
             pass
 
         assert self.compares_by_identity(X)
-        X.__eq__ = foo
+        X.__eq__ = lambda x: None
         assert not self.compares_by_identity(X)
         del X.__eq__
         assert self.compares_by_identity(X)
+
+    def test_versioning(self):
+        class X(object):
+            pass
+
+        class Y(object):
+            def __eq__(self, other):
+                pass
+
+        assert self.get_version() == 0
+        X.__eq__ = lambda x: None
+        # modifying a class for which we never checked the
+        # compares_by_identity() status does not increase the version
+        assert self.get_version() == 0
+
+        del X.__eq__
+        assert self.compares_by_identity(X) # now we check it
+        X.__add__ = lambda x: None
+        assert self.get_version() == 0 # innocent change
+        #
+        X.__eq__ = lambda x: None
+        assert self.get_version() == 1 # BUMP!
+
+        del X.__eq__
+        assert self.compares_by_identity(X)
+        X.__bases__ = (object,)
+        assert self.get_version() == 2 # BUMP!
+
+        # modifying a class which is already "bad" does not increase the
+        # version
+        Y.__eq__ = lambda x: None
+        assert self.get_version() == 2
diff --git a/pypy/objspace/std/typeobject.py b/pypy/objspace/std/typeobject.py
--- a/pypy/objspace/std/typeobject.py
+++ b/pypy/objspace/std/typeobject.py
@@ -150,7 +150,12 @@
             else:
                 w_self.terminator = NoDictTerminator(space, w_self)
 
-    def mutated(w_self):
+    def mutated(w_self, key):
+        """
+        The type is being mutated. key is either the string containing the
+        specific attribute which is being deleted/set or None to indicate a
+        generic mutation.
+        """
         space = w_self.space
         assert w_self.is_heaptype() or space.config.objspace.std.mutable_builtintypes
         if (not space.config.objspace.std.withtypeversion and
@@ -164,8 +169,13 @@
             # ^^^ conservative default, fixed during real usage
 
         if space.config.objspace.std.trackcomparebyidentity:
+            did_compare_by_identity = not w_self.overrides_hash_eq_or_cmp
+            if did_compare_by_identity and (key is None or
+                                            key == '__eq__' or
+                                            key == '__cmp__' or
+                                            key == '__hash__'):
             w_self.overrides_hash_eq_or_cmp = True
-            # ^^^ conservative default, fixed during real usage
+                w_self.space.compares_by_identity_version = VersionTag()
 
         if space.config.objspace.std.newshortcut:
             w_self.w_bltin_new = None
@@ -177,7 +187,7 @@
         subclasses_w = w_self.get_subclasses()
         for w_subclass in subclasses_w:
             assert isinstance(w_subclass, W_TypeObject)
-            w_subclass.mutated()
+            w_subclass.mutated(key)
 
     def version_tag(w_self):
         if (not we_are_jitted() or w_self.is_heaptype() or
@@ -295,7 +305,7 @@
                         w_curr.w_value = w_value
                         return True
                     w_value = TypeCell(w_value)
-        w_self.mutated()
+        w_self.mutated(name)
         w_self.dict_w[name] = w_value
         return True
 
@@ -312,7 +322,7 @@
         except KeyError:
             return False
         else:
-            w_self.mutated()
+            w_self.mutated(key)
             return True
 
     def lookup(w_self, name):
diff --git a/pypy/objspace/std/typetype.py b/pypy/objspace/std/typetype.py
--- a/pypy/objspace/std/typetype.py
+++ b/pypy/objspace/std/typetype.py
@@ -141,7 +141,7 @@
                            w_oldbestbase.getname(space))
 
     # invalidate the version_tag of all the current subclasses
-    w_type.mutated()
+    w_type.mutated(None)
 
     # now we can go ahead and change 'w_type.bases_w'
     saved_bases_w = w_type.bases_w


More information about the pypy-commit mailing list