From 19227fc60b0d7ed23c0553aa9d83c6442528b8e0 Mon Sep 17 00:00:00 2001 From: Yzi-Li <204532581+Yzi-Li@users.noreply.github.com> Date: Sat, 8 Nov 2025 21:21:30 +0800 Subject: [PATCH 1/6] Fix --- Lib/types.py | 4 +++- .../Library/2025-11-08-21-01-46.gh-issue-140972.v960ZZ.rst | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2025-11-08-21-01-46.gh-issue-140972.v960ZZ.rst diff --git a/Lib/types.py b/Lib/types.py index f96c75b46daba7..7c6b78b866b121 100644 --- a/Lib/types.py +++ b/Lib/types.py @@ -211,7 +211,9 @@ def __init__(self, fget=None, fset=None, fdel=None, doc=None): self.fset = fset self.fdel = fdel # next two lines make DynamicClassAttribute act the same as property - self.__doc__ = doc or fget.__doc__ + if doc is None and fget is not None: + doc = fget.__doc__ + self.__doc__ = doc self.overwrite_doc = doc is None # support for abstract methods self.__isabstractmethod__ = bool(getattr(fget, '__isabstractmethod__', False)) diff --git a/Misc/NEWS.d/next/Library/2025-11-08-21-01-46.gh-issue-140972.v960ZZ.rst b/Misc/NEWS.d/next/Library/2025-11-08-21-01-46.gh-issue-140972.v960ZZ.rst new file mode 100644 index 00000000000000..bc8abece71b4a0 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-11-08-21-01-46.gh-issue-140972.v960ZZ.rst @@ -0,0 +1,3 @@ +Now empty docstrings passed to :class:`DynamicClassAttribute` are preserved +when ``fget`` is ``None``; previously ``NoneType.__doc__`` was used +incorrectly. From cce8b69d03f338381848608383d798f2c9c94d43 Mon Sep 17 00:00:00 2001 From: Yzi-Li <204532581+Yzi-Li@users.noreply.github.com> Date: Sat, 8 Nov 2025 21:29:56 +0800 Subject: [PATCH 2/6] Fix typo --- .../next/Library/2025-11-08-21-01-46.gh-issue-140972.v960ZZ.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-11-08-21-01-46.gh-issue-140972.v960ZZ.rst b/Misc/NEWS.d/next/Library/2025-11-08-21-01-46.gh-issue-140972.v960ZZ.rst index bc8abece71b4a0..387ea676fdd4cb 100644 --- a/Misc/NEWS.d/next/Library/2025-11-08-21-01-46.gh-issue-140972.v960ZZ.rst +++ b/Misc/NEWS.d/next/Library/2025-11-08-21-01-46.gh-issue-140972.v960ZZ.rst @@ -1,3 +1,3 @@ -Now empty docstrings passed to :class:`DynamicClassAttribute` are preserved +Now empty docstrings passed to :func:`DynamicClassAttribute` are preserved when ``fget`` is ``None``; previously ``NoneType.__doc__`` was used incorrectly. From e81471a7139d0116bfc84001e17aeb957ef80a69 Mon Sep 17 00:00:00 2001 From: Yzi-Li <204532581+Yzi-Li@users.noreply.github.com> Date: Sat, 8 Nov 2025 22:17:10 +0800 Subject: [PATCH 3/6] Fixup --- Lib/types.py | 5 +++-- .../Library/2025-11-08-21-01-46.gh-issue-140972.v960ZZ.rst | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Lib/types.py b/Lib/types.py index 7c6b78b866b121..d3f1b61b8b58cf 100644 --- a/Lib/types.py +++ b/Lib/types.py @@ -210,11 +210,12 @@ 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 + overwrite_doc = doc is None if doc is None and fget is not None: doc = fget.__doc__ + # next two lines make DynamicClassAttribute act the same as property self.__doc__ = doc - self.overwrite_doc = doc is None + self.overwrite_doc = overwrite_doc # support for abstract methods self.__isabstractmethod__ = bool(getattr(fget, '__isabstractmethod__', False)) diff --git a/Misc/NEWS.d/next/Library/2025-11-08-21-01-46.gh-issue-140972.v960ZZ.rst b/Misc/NEWS.d/next/Library/2025-11-08-21-01-46.gh-issue-140972.v960ZZ.rst index 387ea676fdd4cb..6ce015018db851 100644 --- a/Misc/NEWS.d/next/Library/2025-11-08-21-01-46.gh-issue-140972.v960ZZ.rst +++ b/Misc/NEWS.d/next/Library/2025-11-08-21-01-46.gh-issue-140972.v960ZZ.rst @@ -1,3 +1,3 @@ -Now empty docstrings passed to :func:`DynamicClassAttribute` are preserved -when ``fget`` is ``None``; previously ``NoneType.__doc__`` was used +Now empty docstrings passed to :func:`types.DynamicClassAttribute` are +preserved when ``fget`` is ``None``; previously ``NoneType.__doc__`` was used incorrectly. From 78bebb35d5bab9dfd6f70df9f3cf3b6796e72203 Mon Sep 17 00:00:00 2001 From: Yzi-Li <204532581+Yzi-Li@users.noreply.github.com> Date: Sat, 8 Nov 2025 22:28:57 +0800 Subject: [PATCH 4/6] Add test --- Lib/test/test_dynamicclassattribute.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Lib/test/test_dynamicclassattribute.py b/Lib/test/test_dynamicclassattribute.py index b19be33c72f94b..1e2fd1a9eca6f2 100644 --- a/Lib/test/test_dynamicclassattribute.py +++ b/Lib/test/test_dynamicclassattribute.py @@ -195,6 +195,17 @@ def __init__(self): Okay2.color self.assertEqual(Okay2().color, 'magenta') + @unittest.skipIf(sys.flags.optimize >= 2, + "Docstrings are omitted with -O2 and above") + def test_empty_docstring(self): + attr = DynamicClassAttribute(fget=None, fset=None, fdel=None, doc='') + self.assertEqual(attr.__doc__, '',) + def fget(): + """fget's docstring""" + attr_with_fget = DynamicClassAttribute(fget=fget, doc='') + self.assertEqual(attr_with_fget.__doc__, '') + attr_no_doc = DynamicClassAttribute(fget=None) + self.assertIsNone(attr_no_doc.__doc__) # Issue 5890: subclasses of DynamicClassAttribute do not preserve method __doc__ strings class PropertySub(DynamicClassAttribute): From 23e1fc5da90e9d04b4ab921a9922977117d66bca Mon Sep 17 00:00:00 2001 From: Yzi-Li <204532581+Yzi-Li@users.noreply.github.com> Date: Sun, 9 Nov 2025 08:27:18 +0800 Subject: [PATCH 5/6] Use support.requires_docstrings --- Lib/test/test_dynamicclassattribute.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_dynamicclassattribute.py b/Lib/test/test_dynamicclassattribute.py index 1e2fd1a9eca6f2..c583cbcc216a4f 100644 --- a/Lib/test/test_dynamicclassattribute.py +++ b/Lib/test/test_dynamicclassattribute.py @@ -4,6 +4,7 @@ import abc import sys import unittest +from test import support from types import DynamicClassAttribute class PropertyBase(Exception): @@ -195,8 +196,7 @@ def __init__(self): Okay2.color self.assertEqual(Okay2().color, 'magenta') - @unittest.skipIf(sys.flags.optimize >= 2, - "Docstrings are omitted with -O2 and above") + @support.requires_docstrings def test_empty_docstring(self): attr = DynamicClassAttribute(fget=None, fset=None, fdel=None, doc='') self.assertEqual(attr.__doc__, '',) From b2f40d78c1fa288d56e4dd143712168fc8472977 Mon Sep 17 00:00:00 2001 From: Yongzi Li <204532581+Yzi-Li@users.noreply.github.com> Date: Tue, 11 Nov 2025 17:40:09 +0800 Subject: [PATCH 6/6] Update Lib/test/test_dynamicclassattribute.py Co-authored-by: Jelle Zijlstra --- Lib/test/test_dynamicclassattribute.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_dynamicclassattribute.py b/Lib/test/test_dynamicclassattribute.py index c583cbcc216a4f..bc89a2907c6ebf 100644 --- a/Lib/test/test_dynamicclassattribute.py +++ b/Lib/test/test_dynamicclassattribute.py @@ -199,7 +199,7 @@ def __init__(self): @support.requires_docstrings def test_empty_docstring(self): attr = DynamicClassAttribute(fget=None, fset=None, fdel=None, doc='') - self.assertEqual(attr.__doc__, '',) + self.assertEqual(attr.__doc__, '') def fget(): """fget's docstring""" attr_with_fget = DynamicClassAttribute(fget=fget, doc='')