https://github.com/python/cpython/commit/0a39730ecd45430e14cfeb244980b6eaa3571695
commit: 0a39730ecd45430e14cfeb244980b6eaa3571695
branch: main
author: Daniel Hollas <[email protected]>
committer: JelleZijlstra <[email protected]>
date: 2026-04-25T22:57:38-07:00
summary:

gh-137855: Lazy import `inspect` module in dataclasses (#144387)



Co-authored-by: Hugo van Kemenade <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-02-12-18-05-16.gh-issue-137855.2_PTbg.rst
M Lib/dataclasses.py
M Lib/test/test__colorize.py
M Lib/test/test_dataclasses/__init__.py

diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py
index 988edfed6f4dcb..df192763c5bd15 100644
--- a/Lib/dataclasses.py
+++ b/Lib/dataclasses.py
@@ -1,12 +1,12 @@
 import sys
 import types
-import inspect
 import keyword
 import itertools
 import annotationlib
 import abc
 from reprlib import recursive_repr
 lazy import copy
+lazy import inspect
 lazy import re
 
 
@@ -988,6 +988,28 @@ def _hash_exception(cls, fields, func_builder):
 # See https://bugs.python.org/issue32929#msg312829 for an if-statement
 # version of this table.
 
+# A non-data descriptor to autogenerate class docstring
+# from the signature of its __init__ method on demand.
+# The primary reason is to be able to lazy import `inspect` module.
+class _AutoDocstring:
+
+    def __get__(self, _obj, cls):
+        try:
+            # In some cases fetching a signature is not possible.
+            # But, we surely should not fail in this case.
+            text_sig = str(inspect.signature(
+                 cls,
+                 annotation_format=annotationlib.Format.FORWARDREF,
+            )).replace(' -> None', '')
+        except TypeError, ValueError:
+            text_sig = ''
+
+        doc = cls.__name__ + text_sig
+        setattr(cls, '__doc__', doc)
+        return doc
+
+_auto_docstring = _AutoDocstring()
+
 
 def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen,
                    match_args, kw_only, slots, weakref_slot):
@@ -1215,23 +1237,13 @@ def _process_class(cls, init, repr, eq, order, 
unsafe_hash, frozen,
     if hash_action:
         cls.__hash__ = hash_action(cls, field_list, func_builder)
 
-    # Generate the methods and add them to the class.  This needs to be done
-    # before the __doc__ logic below, since inspect will look at the __init__
-    # signature.
+    # Generate the methods and add them to the class.
     func_builder.add_fns_to_class(cls)
 
     if not getattr(cls, '__doc__'):
-        # Create a class doc-string.
-        try:
-            # In some cases fetching a signature is not possible.
-            # But, we surely should not fail in this case.
-            text_sig = str(inspect.signature(
-                cls,
-                annotation_format=annotationlib.Format.FORWARDREF,
-            )).replace(' -> None', '')
-        except (TypeError, ValueError):
-            text_sig = ''
-        cls.__doc__ = (cls.__name__ + text_sig)
+        # Create a class doc-string lazily via descriptor protocol
+        # to avoid importing `inspect` module.
+        cls.__doc__ = _auto_docstring
 
     if match_args:
         # I could probably compute this once.
@@ -1391,8 +1403,10 @@ def _add_slots(cls, is_frozen, weakref_slot, 
defined_fields):
     # make an update, since all closures for a class will share a
     # given cell.
     for member in newcls.__dict__.values():
+
         # If this is a wrapped function, unwrap it.
-        member = inspect.unwrap(member)
+        if not isinstance(member, type) and hasattr(member, '__wrapped__'):
+            member = inspect.unwrap(member)
 
         if isinstance(member, types.FunctionType):
             if _update_func_cell_for__class__(member, cls, newcls):
diff --git a/Lib/test/test__colorize.py b/Lib/test/test__colorize.py
index 0353ff7530b92a..48fa52bfd5672c 100644
--- a/Lib/test/test__colorize.py
+++ b/Lib/test/test__colorize.py
@@ -28,7 +28,7 @@ class TestImportTime(unittest.TestCase):
     @cpython_only
     def test_lazy_import(self):
         import_helper.ensure_lazy_imports(
-            "_colorize", {"copy", "re"}
+            "_colorize", {"copy", "re", "inspect"}
         )
 
 
diff --git a/Lib/test/test_dataclasses/__init__.py 
b/Lib/test/test_dataclasses/__init__.py
index 6ff82b8810abed..5eec9e23cd414b 100644
--- a/Lib/test/test_dataclasses/__init__.py
+++ b/Lib/test/test_dataclasses/__init__.py
@@ -27,11 +27,21 @@
 import dataclasses  # Needed for the string "dataclasses.InitVar[int]" to work 
as an annotation.
 
 from test import support
-from test.support import import_helper
+from test.support import cpython_only, import_helper
 
 # Just any custom exception we can catch.
 class CustomError(Exception): pass
 
+
+class TestImportTime(unittest.TestCase):
+
+    @cpython_only
+    def test_lazy_import(self):
+        import_helper.ensure_lazy_imports(
+            "dataclasses", {"inspect", "re", "copy"}
+        )
+
+
 class TestCase(unittest.TestCase):
     def test_no_fields(self):
         @dataclass
@@ -2309,6 +2319,20 @@ class C:
 
         self.assertDocStrEqual(C.__doc__, "C()")
 
+    def test_docstring_slotted(self):
+        @dataclass(slots=True)
+        class C:
+            x: int
+
+        self.assertDocStrEqual(C.__doc__, "C(x:int)")
+
+    def test_docstring_recursive(self):
+        @dataclass()
+        class C:
+            x: list[C]
+
+        self.assertDocStrEqual(C.__doc__, 
"C(x:list[test.test_dataclasses.TestDocString.test_docstring_recursive.<locals>.C])")
+
     def test_docstring_one_field(self):
         @dataclass
         class C:
diff --git 
a/Misc/NEWS.d/next/Library/2026-02-12-18-05-16.gh-issue-137855.2_PTbg.rst 
b/Misc/NEWS.d/next/Library/2026-02-12-18-05-16.gh-issue-137855.2_PTbg.rst
new file mode 100644
index 00000000000000..586c7d3495ae26
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-02-12-18-05-16.gh-issue-137855.2_PTbg.rst
@@ -0,0 +1 @@
+Reduce the import time of :mod:`dataclasses` module by ~20%.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to