From 9461e298b5c070eb7e50e9a23c886682e2172646 Mon Sep 17 00:00:00 2001 From: Dong-hee Na Date: Sat, 14 Jul 2018 23:34:14 +0900 Subject: [PATCH] bpo-33668: Fix inspect.getmembers to traverse mro when object is class --- Lib/inspect.py | 22 +++++++------- Lib/test/test_inspect.py | 30 +++++++++++++++++++ .../2018-07-14-23-52-12.bpo-33668.7poHxA.rst | 1 + 3 files changed, 41 insertions(+), 12 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2018-07-14-23-52-12.bpo-33668.7poHxA.rst 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.