Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 30 additions & 16 deletions Lib/dataclasses.py
Original file line numberDiff line numberDiff line change
@@ -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


Expand DownExpand Up@@ -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):
Expand DownExpand Up@@ -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.
Expand DownExpand Up@@ -1383,8 +1395,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__'):

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check was copied from the while loop in inspect.unwrap

member = inspect.unwrap(member)

if isinstance(member, types.FunctionType):
if _update_func_cell_for__class__(member, cls, newcls):
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test__colorize.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -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"}
)


Expand Down
26 changes: 25 additions & 1 deletion Lib/test/test_dataclasses/__init__.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
Expand DownExpand Up@@ -2295,6 +2305,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:
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
Reduce the import time of :mod:`dataclasses` module by ~20%.
Loading