diff --git a/Lib/inspect.py b/Lib/inspect.py index a956acf310f85b..542eeef79a5ab1 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -486,6 +486,14 @@ def getmembers(object, predicate=None): Attribute = namedtuple('Attribute', 'name kind defining_class object') +def _is_class_property(cls, name): + for base in cls.__mro__: + o = base.__dict__.get(name) + if isinstance(o, classmethod) and \ + isinstance(o.__func__, property): + return True + return False + def classify_class_attrs(cls): """Return list of attribute-descriptor tuples. @@ -546,6 +554,8 @@ def classify_class_attrs(cls): try: if name == '__dict__': raise Exception("__dict__ is special, don't want the proxy") + if _is_class_property(cls, name): + raise Exception("class property") get_obj = getattr(cls, name) except Exception as exc: pass diff --git a/Lib/pydoc.py b/Lib/pydoc.py index 3a2ff218f8319a..617ddf57abf968 100755 --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -885,6 +885,8 @@ def spill(msg, attrs, predicate): push(msg) for name, kind, homecls, value in ok: try: + if inspect._is_class_property(object, name): + raise Exception("class property") value = getattr(object, name) except Exception: # Some descriptors may meet a failure in their __get__. @@ -1368,6 +1370,8 @@ def spill(msg, attrs, predicate): push(msg) for name, kind, homecls, value in ok: try: + if inspect._is_class_property(object, name): + raise Exception("class property") value = getattr(object, name) except Exception: # Some descriptors may meet a failure in their __get__. diff --git a/Lib/test/test_pydoc.py b/Lib/test/test_pydoc.py index 0a7d72c768424b..328f8719faace8 100644 --- a/Lib/test/test_pydoc.py +++ b/Lib/test/test_pydoc.py @@ -514,6 +514,19 @@ class object " | ... and \\d+ other subclasses") self.assertRegex(text, snip) + def test_classmethod_property(self): + # Issue 45356 + class A: + @classmethod + @property + def a(self): + # BaseException because accessor of attributes in pydoc and in + # inspect catches Exception + raise BaseException + class B(A): pass + pydoc.render_doc(A) + pydoc.render_doc(B) + def test_builtin_with_child(self): """Tests help on builtin object which have only child classes. diff --git a/Misc/NEWS.d/next/Library/2021-10-28-22-29-30.bpo-45356.jCCqdb.rst b/Misc/NEWS.d/next/Library/2021-10-28-22-29-30.bpo-45356.jCCqdb.rst new file mode 100644 index 00000000000000..6fb8e9a60a4f30 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-10-28-22-29-30.bpo-45356.jCCqdb.rst @@ -0,0 +1,4 @@ +:mod:`pydoc` ``help`` command, :meth:`~pydoc.HTMLDoc.docclass`, +:meth:`~pydoc.TextDoc.docclass`, and :func:`~inspect.classify_class_attrs` +will no longer execute the logic of a class property when examining a class +object that contains such property.