Author: Antonio Cuni <[email protected]>
Branch: identity-dict-strategy
Changeset: r45805:76c609d60ebd
Date: 2011-07-21 12:11 +0200
http://bitbucket.org/pypy/pypy/changeset/76c609d60ebd/
Log: kill the global versioning logic, and add a big comment which
explains why it's not needed
diff --git a/pypy/objspace/std/identitydict.py
b/pypy/objspace/std/identitydict.py
--- a/pypy/objspace/std/identitydict.py
+++ b/pypy/objspace/std/identitydict.py
@@ -1,3 +1,6 @@
+## ----------------------------------------------------------------------------
+## dict strategy (see dict_multiobject.py)
+
from pypy.rlib import rerased
from pypy.objspace.std.dictmultiobject import (AbstractTypedStrategy,
DictStrategy,
@@ -5,39 +8,6 @@
_UnwrappedIteratorMixin)
-# a global (per-space) 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() and objecttype:descr_setclass
-
-def bump_global_version(space):
- if space.config.objspace.std.withidentitydict:
- space.fromcache(ComparesByIdentityVersion).bump()
-
-def get_global_version(space):
- if space.config.objspace.std.withidentitydict:
- return space.fromcache(ComparesByIdentityVersion).get()
- return None
-
-class ComparesByIdentityVersion(object):
-
- def __init__(self, space):
- self.bump()
-
- def bump(self):
- from pypy.objspace.std.typeobject import VersionTag
- self._version = VersionTag()
-
- def get(self):
- return self._version
-
-
-
-## ----------------------------------------------------------------------------
-## dict strategy (see dict_multiobject.py)
-
# this strategy is selected by EmptyDictStrategy.switch_to_correct_strategy
class IdentityDictStrategy(AbstractTypedStrategy, DictStrategy):
"""
@@ -45,11 +15,48 @@
default unless you override __hash__, __eq__ or __cmp__). The storage is
just a normal RPython dict, which has already the correct by-identity
semantics.
+
+ Note that at a first sight, you might have problems if you mutate the
+ class of an object which is already inside an identitydict. Consider this
+ example::
+
+ class X(object):
+ pass
+ d = {x(): 1}
+ X.__eq__ = ...
+ d[y] # might trigger a call to __eq__?
+
+ We want to be sure that x.__eq__ is called in the same cases as in
+ CPython. However, as long as the strategy is IdentityDictStrategy, the
+ __eq__ will never be called.
+
+ It turns out that it's not a problem. In CPython (and in PyPy without
+ this strategy), the __eq__ is called if ``hash(y) == hash(x)`` and ``x is
+ not y``. Note that hash(x) is computed at the time when we insert x in
+ the dict, not at the time we lookup y.
+
+ Now, how can hash(y) == hash(x)? There are two possibilities:
+
+ 1. we write a custom __hash__ for the class of y, thus making it a not
+ "compares by reference" type
+
+ 2. the class of y is "compares by reference" type, and by chance the
+ hash is the same as x
+
+ In the first case, the getitem immediately notice that y is not of the
+ right type, and switches the strategy to ObjectDictStrategy, then the
+ lookup works as usual.
+
+ The second case is completely non-deterministic, even in CPython.
+ Depending on the phase of the moon, you might call the __eq__ or not, so
+ it is perfectly fine to *never* call it. Morever, in practice with the
+ minimar GC we never have two live objects with the same hash, so it would
+ never happen anyway.
"""
- _erase_tuple, _unerase_tuple = rerased.new_erasing_pair("identitydict")
- _erase_tuple = staticmethod(_erase_tuple)
- _unerase_tuple = staticmethod(_unerase_tuple)
+ erase, unerase = rerased.new_erasing_pair("identitydict")
+ erase = staticmethod(erase)
+ unerase = staticmethod(unerase)
def wrap(self, unwrapped):
return unwrapped
@@ -57,18 +64,6 @@
def unwrap(self, wrapped):
return wrapped
- def erase(self, d):
- current_version = get_global_version(self.space)
- return self._erase_tuple((current_version, d))
-
- def unerase(self, dstorage):
- version, d = self._unerase_tuple(dstorage)
- return d
-
- def get_current_version(self, dstorage):
- version, d = self._unerase_tuple(dstorage)
- return version
-
def get_empty_storage(self):
return self.erase({})
diff --git a/pypy/objspace/std/objecttype.py b/pypy/objspace/std/objecttype.py
--- a/pypy/objspace/std/objecttype.py
+++ b/pypy/objspace/std/objecttype.py
@@ -43,9 +43,6 @@
assert isinstance(w_oldcls, W_TypeObject)
if w_oldcls.get_full_instance_layout() ==
w_newcls.get_full_instance_layout():
w_obj.setclass(space, w_newcls)
- if space.config.objspace.std.withidentitydict:
- if w_oldcls.compares_by_identity() and not
w_newcls.compares_by_identity():
- identitydict.bump_global_version(space)
else:
raise operationerrfmt(space.w_TypeError,
"__class__ assignment: '%s' object layout
differs from '%s'",
diff --git a/pypy/objspace/std/test/test_identitydict.py
b/pypy/objspace/std/test/test_identitydict.py
--- a/pypy/objspace/std/test/test_identitydict.py
+++ b/pypy/objspace/std/test/test_identitydict.py
@@ -2,7 +2,7 @@
from pypy.conftest import gettestobjspace
from pypy.conftest import option
-class AppTestTrackVersion:
+class AppTestComparesByIdentity:
def setup_class(cls):
from pypy.objspace.std import identitydict
@@ -13,15 +13,6 @@
return space.wrap(w_cls.compares_by_identity())
cls.w_compares_by_identity =
cls.space.wrap(interp2app(compares_by_identity))
- def get_version(space):
- v = cls.versions.setdefault(identitydict.get_global_version(space),
- len(cls.versions))
- return space.wrap(v)
- cls.w_get_version = cls.space.wrap(interp2app(get_version))
-
- def setup_method(self, m):
- self.__class__.versions = {}
-
def test_compares_by_identity(self):
class Plain(object):
pass
@@ -53,56 +44,6 @@
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
-
- def test_change___class__(self):
- class X(object):
- pass
-
- class Y(object):
- pass
-
- class Z(object):
- def __eq__(self, other):
- pass
-
- x = X()
- assert self.compares_by_identity(X)
- assert self.get_version() == 0
- x.__class__ = Y
- assert self.get_version() == 0
- x.__class__ = Z
- assert self.get_version() == 1
class AppTestIdentityDict(object):
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
@@ -178,8 +178,6 @@
if (key is None or key == '__eq__' or
key == '__cmp__' or key == '__hash__'):
w_self.compares_by_identity_status = UNKNOWN
- if did_compare_by_identity:
- identitydict.bump_global_version(w_self.space)
if space.config.objspace.std.newshortcut:
w_self.w_bltin_new = None
_______________________________________________
pypy-commit mailing list
[email protected]
http://mail.python.org/mailman/listinfo/pypy-commit