diff -r 3752c94368dd Lib/inspect.py --- a/Lib/inspect.py Sun Oct 13 10:52:10 2013 -0700 +++ b/Lib/inspect.py Mon Oct 14 10:31:11 2013 -0700 @@ -329,21 +329,21 @@ def classify_class_attrs(cls): """ mro = getmro(cls) metamro = getmro(type(cls)) # for attributes stored in the metaclass metamro = tuple([cls for cls in metamro if cls not in (type, object)]) possible_bases = (cls,) + mro + metamro names = dir(cls) # add any virtual attributes to the list of names # this may result in duplicate entries if, for example, a virtual # attribute with the same name as a member property exists - for base in cls.__bases__: + for base in mro: for k, v in base.__dict__.items(): if isinstance(v, types.DynamicClassAttribute): names.append(k) result = [] processed = set() sentinel = object() for name in names: # Get the object associated with the name, and where it was defined. # Normal objects will be looked up with both getattr and directly in # its class' dict (in case getattr fails [bug #1785], and also to look @@ -357,42 +357,49 @@ def classify_class_attrs(cls): get_obj = sentinel dict_obj = sentinel if name not in processed: try: get_obj = getattr(cls, name) except Exception as exc: pass else: - homecls = getattr(get_obj, "__class__") homecls = getattr(get_obj, "__objclass__", homecls) if homecls not in possible_bases: # if the resulting object does not live somewhere in the - # mro, drop it and go with the dict_obj version only + # mro, drop it and search the mro manually homecls = None - get_obj = sentinel + last_cls = None + last_obj = None + for srch_cls in ((cls,) + mro): + srch_obj = getattr(srch_cls, name, None) + if srch_obj is get_obj: + last_cls = srch_cls + last_obj = srch_obj + if last_cls is not None: + homecls = last_cls for base in possible_bases: if name in base.__dict__: dict_obj = base.__dict__[name] homecls = homecls or base break # Classify the object or its descriptor. if get_obj is not sentinel: obj = get_obj else: obj = dict_obj - if isinstance(obj, staticmethod): + if isinstance(dict_obj, staticmethod): kind = "static method" - elif isinstance(obj, classmethod): + elif isinstance(dict_obj, classmethod): kind = "class method" elif isinstance(obj, property): kind = "property" elif isfunction(obj) or ismethoddescriptor(obj): kind = "method" else: kind = "data" result.append(Attribute(name, kind, homecls, obj)) processed.add(name) diff -r 3752c94368dd Lib/test/test_inspect.py --- a/Lib/test/test_inspect.py Sun Oct 13 10:52:10 2013 -0700 +++ b/Lib/test/test_inspect.py Mon Oct 14 10:31:11 2013 -0700 @@ -649,27 +649,83 @@ class TestClassesAndFunctions(unittest.T self.assertIn(('dd', 'data', A), attrs, 'missing data descriptor') def test_classify_builtin_types(self): # Simple sanity check that all built-in types can have their # attributes classified. for name in dir(__builtins__): builtin = getattr(__builtins__, name) if isinstance(builtin, type): inspect.classify_class_attrs(builtin) - def test_classify_VirtualAttribute(self): - class VA: + def test_classify_DynamicClassAttribute(self): + class Meta(type): + def __getattr__(self, name): + if name == 'ham': + return 'spam' + return super().__getattr__(name) + class VA(metaclass=Meta): @types.DynamicClassAttribute def ham(self): return 'eggs' - should_find = inspect.Attribute('ham', 'data', VA, VA.__dict__['ham']) - self.assertIn(should_find, inspect.classify_class_attrs(VA)) + should_find_dca = inspect.Attribute('ham', 'data', VA, VA.__dict__['ham']) + self.assertIn(should_find_dca, inspect.classify_class_attrs(VA)) + should_find_ga = inspect.Attribute('ham', 'data', VA, 'spam') + self.assertIn(should_find_ga, inspect.classify_class_attrs(VA)) + + def test_classify_VirtualAttribute(self): + class Meta(type): + def __dir__(cls): + return ['__class__', '__module__', '__name__', 'BOOM'] + def __getattr__(self, name): + if name =='BOOM': + return 42 + return super().__getattr(name) + class Class(metaclass=Meta): + pass + should_find = inspect.Attribute('BOOM', 'data', Class, 42) + self.assertIn(should_find, inspect.classify_class_attrs(Class)) + + def test_classify_VirtualAttribute_multi_classes(self): + class Meta1(type): + def __dir__(cls): + return ['__class__', '__module__', '__name__', 'one'] + def __getattr__(self, name): + if name =='one': + return 1 + return super().__getattr__(name) + class Meta2(type): + def __dir__(cls): + return ['__class__', '__module__', '__name__', 'two'] + def __getattr__(self, name): + if name =='two': + return 2 + return super().__getattr__(name) + class Meta3(Meta1, Meta2): + def __dir__(cls): + return list(sorted(set(['__class__', '__module__', '__name__', 'three'] + + Meta1.__dir__(cls) + Meta2.__dir__(cls)))) + def __getattr__(self, name): + if name =='three': + return 3 + return super().__getattr__(name) + class Class1(metaclass=Meta1): + pass + class Class2(Class1, metaclass=Meta3): + pass + + should_find1 = inspect.Attribute('one', 'data', Class1, 1) + should_find2 = inspect.Attribute('two', 'data', Class2, 2) + should_find3 = inspect.Attribute('three', 'data', Class2, 3) + cca = inspect.classify_class_attrs(Class2) + for sf in (should_find1, should_find2, should_find3): + self.assertIn(sf, cca) + def test_getmembers_descriptors(self): class A(object): dd = _BrokenDataDescriptor() md = _BrokenMethodDescriptor() def pred_wrapper(pred): # A quick'n'dirty way to discard standard attributes of new-style # classes. class Empty(object): diff -r 3752c94368dd Lib/types.py --- a/Lib/types.py Sun Oct 13 10:52:10 2013 -0700 +++ b/Lib/types.py Mon Oct 14 10:31:11 2013 -0700 @@ -110,21 +110,21 @@ class DynamicClassAttribute: This allows one to have properties active on an instance, and have virtual attributes on the class with the same name (see Enum for an example). """ def __init__(self, fget=None, fset=None, fdel=None, doc=None): self.fget = fget self.fset = fset self.fdel = fdel # next two lines make DynamicClassAttribute act the same as property - self.__doc__ = doc or fget.__doc__ or self.__doc__ + self.__doc__ = doc or fget.__doc__ self.overwrite_doc = doc is None # support for abstract methods self.__isabstractmethod__ = bool(getattr(fget, '__isabstractmethod__', False)) def __get__(self, instance, ownerclass=None): if instance is None: if self.__isabstractmethod__: return self raise AttributeError() elif self.fget is None: