Index: Lib/inspect.py =================================================================== --- Lib/inspect.py (Revision 88454) +++ Lib/inspect.py (Arbeitskopie) @@ -1065,19 +1065,22 @@ def _check_instance(obj, attr): instance_dict = {} - try: - instance_dict = object.__getattribute__(obj, "__dict__") - except AttributeError: - pass - return instance_dict.get(attr, _sentinel) + if not _dict_shadowed(type(obj)): + try: + instance_dict = object.__getattribute__(obj, "__dict__") + except AttributeError: + pass + return dict.get(instance_dict, attr, _sentinel) + else: + return _sentinel - def _check_class(klass, attr): for entry in _static_getmro(klass): - try: - return entry.__dict__[attr] - except KeyError: - pass + if not _dict_shadowed(type(klass)): + try: + return entry.__dict__[attr] + except KeyError: + pass return _sentinel def _is_type(obj): @@ -1087,6 +1090,20 @@ return False return True +def _dict_shadowed(klass): + dict_attr = type.__dict__["__dict__"] + for entry in _static_getmro(klass): + try: + class_dict = dict_attr.__get__(entry)["__dict__"] + except KeyError: + pass + else: + if not (type(class_dict) is types.GetSetDescriptorType and + class_dict.__name__ == "__dict__" and + class_dict.__objclass__ is entry): + return True + # No __dict__ attribute found at all, means it can't be shadowed + return False def getattr_static(obj, attr, default=_sentinel): """Retrieve attributes without triggering dynamic lookup via the @@ -1121,10 +1138,11 @@ if obj is klass: # for types we check the metaclass too for entry in _static_getmro(type(klass)): - try: - return entry.__dict__[attr] - except KeyError: - pass + if not _dict_shadowed(type(entry)): + try: + return entry.__dict__[attr] + except KeyError: + pass if default is not _sentinel: return default raise AttributeError(attr) Index: Lib/test/test_inspect.py =================================================================== --- Lib/test/test_inspect.py (Revision 88454) +++ Lib/test/test_inspect.py (Arbeitskopie) @@ -906,7 +906,58 @@ self.assertEqual(inspect.getattr_static(Something(), 'foo'), 3) self.assertEqual(inspect.getattr_static(Something, 'foo'), 3) + def test_custom_instance_dict(self): + sentinel = object() + class MyDict(dict): + def get(self, key, default=sentinel): + self["executed"] = True + if default is sentinel: + return super().get(key) + else: + return super().get(key, default) + + class Thing(object): + def __init__(self): + self.__dict__ = MyDict() + self.__dict__["executed"] = False + self.__dict__["key"] = 42 + + instance = Thing() + self.assertEqual(inspect.getattr_static(instance, "key"), 42) + self.assertFalse(instance.executed) + + def test_dict_as_property(self): + class Thing(object): + spam = 42 + executed = False + + @property + def __dict__(self): + self.executed = True + return dict(spam=23) + + instance = Thing() + self.assertEquals(inspect.getattr_static(instance, "spam"), 42) + self.assertFalse(instance.executed) + + def test_metaclass_dict_as_property(self): + class Meta(type): + @property + def __dict__(self): + self.executed = True + + class Thing(metaclass=Meta): + executed = False + + def __init__(self): + self.spam = 42 + + instance = Thing() + self.assertEquals(inspect.getattr_static(instance, "spam"), 42) + self.assertFalse(Thing.executed) + + class TestGetGeneratorState(unittest.TestCase): def setUp(self):