diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index 730ced7299865e..06c5d627a11a4a 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -2,7 +2,6 @@ import sys import copy import types -import inspect import keyword import itertools import annotationlib @@ -982,6 +981,29 @@ def _hash_exception(cls, fields, func_builder): # See https://bugs.python.org/issue32929#msg312829 for an if-statement # version of this table. +class AutoDocstring: + """A non-data descriptor to autogenerate class docstring + from the signature of its __init__ method. + """ + + def __get__(self, _obj, cls): + # TODO: Make this top-level lazy import once PEP810 lands + import inspect + + 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 + def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen, match_args, kw_only, slots, weakref_slot): @@ -1209,23 +1231,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__ = AutoDocstring() if match_args: # I could probably compute this once. @@ -1377,8 +1389,12 @@ 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__'): + # TODO: Make this top-level lazy import once PEP810 lands + import inspect + member = inspect.unwrap(member) if isinstance(member, types.FunctionType): if _update_func_cell_for__class__(member, cls, newcls): diff --git a/Lib/inspect.py b/Lib/inspect.py index 0dba3c6628c6e5..8f7908c589959c 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -165,7 +165,7 @@ from collections import namedtuple, OrderedDict from weakref import ref as make_weakref -# Create constants for the compiler flags in Include/code.h +# Create constants for the compiler flags in Include/cpython/code.h # We try to get them from dis to avoid duplication mod_dict = globals() for k, v in dis.COMPILER_FLAG_NAMES.items(): diff --git a/Lib/test/test_dataclasses/__init__.py b/Lib/test/test_dataclasses/__init__.py index 3b335429b98500..35edd014f71088 100644 --- a/Lib/test/test_dataclasses/__init__.py +++ b/Lib/test/test_dataclasses/__init__.py @@ -2295,6 +2295,13 @@ 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_one_field(self): @dataclass class C: