diff --git a/Lib/inspect.py b/Lib/inspect.py index b8a142232b88a0..53e96801d9137a 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -319,23 +319,21 @@ def isabstract(object): def getmembers(object, predicate=None): """Return all members of an object as (name, value) pairs sorted by name. Optionally, only return members that satisfy a given predicate.""" - if isclass(object): - mro = (object,) + getmro(object) - else: - mro = () - results = [] - processed = set() names = dir(object) - # :dd any DynamicClassAttributes to the list of names if object is a class; - # this may result in duplicate entries if, for example, a virtual - # attribute with the same name as a DynamicClassAttribute exists - try: + if isclass(object): + mro = getmro(object) + # Add any DynamicClassAttributes to the list of names + # if object is a class; + # this may result in duplicate entries if, for example, a virtual + # attribute with the same name as a DynamicClassAttribute exists for base in object.__bases__: for k, v in base.__dict__.items(): if isinstance(v, types.DynamicClassAttribute): names.append(k) - except AttributeError: - pass + else: + mro = () + results = [] + processed = set() for key in names: # First try to get the value via getattr. Some descriptors don't # like calling their __get__ (see bug #1785), so fall back to diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py index b9072e0137eb08..26a4bd037edd5e 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -1171,6 +1171,36 @@ class C(metaclass=M): attrs = [a[0] for a in inspect.getmembers(C)] self.assertNotIn('missing', attrs) + def test_getmembers_with_non_class(self): + name = '__inspect_dummy' + m = types.ModuleType(name) + m.__bases__ = None + attrs = [a[0] for a in inspect.getmembers(m)] + self.assertIn('__bases__', attrs) + + def test_getmembers_with_custom_getattr(self): + class CustomGetattr(object): + def __getattr__(self, attr): + return None + + CustomGetattr.__test__ = None + attrs = [a[0] for a in inspect.getmembers(CustomGetattr())] + self.assertIn('__test__', attrs) + + def test_getmembers_with_dynamic_class_attribute(self): + class A: + @types.DynamicClassAttribute + def ham(self): + return 'eggs' + class B(A): pass + class C(B): pass + + attrs = [b[0] for b in inspect.getmembers(B)] + self.assertIn('ham', attrs) + attrs = [c[0] for c in inspect.getmembers(C)] + self.assertIn('ham', attrs) + + class TestIsDataDescriptor(unittest.TestCase): def test_custom_descriptors(self): diff --git a/Misc/NEWS.d/next/Library/2018-07-14-23-52-12.bpo-33668.7poHxA.rst b/Misc/NEWS.d/next/Library/2018-07-14-23-52-12.bpo-33668.7poHxA.rst new file mode 100644 index 00000000000000..0776187becd0ed --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-07-14-23-52-12.bpo-33668.7poHxA.rst @@ -0,0 +1 @@ +Fix :func:`inspect.getmembers` to traversing mro when the object is a class only.