From 4fbde9c7d24056a5f45871b307c5307453b447be Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Tue, 21 Apr 2026 01:42:06 -0500 Subject: [PATCH 1/2] Defer inspect import to reduce import time by ~24% MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move `import inspect` from module level in `_compat.py` and `_make.py` into the functions that use it. Lazy-load `converters` and `validators` submodules from `attr/__init__.py` and `attrs/__init__.py` since they trigger class building (which invokes inspect) at import time. inspect is only used in three places, all at class-build time: - _compat._AnnotationExtractor.__init__() for signature extraction - _make.py line 709 for __attrs_pre_init__ arg detection - _make.py line 931 for cached_property return annotations Benchmark (Python 3.13, PYTHONDONTWRITEBYTECODE=1, 50 runs): - Before: 85.0ms ± 20.4ms - After: 64.7ms ± 12.0ms - Improvement: ~20ms (24%) All 1385 tests pass. --- src/attr/__init__.py | 12 +++++++++++- src/attr/_compat.py | 7 ++++++- src/attr/_make.py | 5 ++++- src/attrs/__init__.py | 2 +- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/attr/__init__.py b/src/attr/__init__.py index 5c6e0650b..11215c3d1 100644 --- a/src/attr/__init__.py +++ b/src/attr/__init__.py @@ -7,7 +7,7 @@ from functools import partial from typing import Callable, Literal, Protocol -from . import converters, exceptions, filters, setters, validators +from . import exceptions, filters, setters from ._cmp import cmp_using from ._config import get_run_validators, set_run_validators from ._funcs import asdict, assoc, astuple, has, resolve_types @@ -78,6 +78,9 @@ class AttrsInstance(Protocol): ] +_LAZY_SUBMODULES = {"converters", "validators"} + + def _make_getattr(mod_name: str) -> Callable: """ Create a metadata proxy for packaging information that uses *mod_name* in @@ -85,6 +88,13 @@ def _make_getattr(mod_name: str) -> Callable: """ def __getattr__(name: str) -> str: + if name in _LAZY_SUBMODULES: + import importlib + + mod = importlib.import_module(f".{name}", mod_name) + globals()[name] = mod + return mod + if name not in ("__version__", "__version_info__"): msg = f"module {mod_name} has no attribute {name}" raise AttributeError(msg) diff --git a/src/attr/_compat.py b/src/attr/_compat.py index bc68ed9ea..486a1ab3a 100644 --- a/src/attr/_compat.py +++ b/src/attr/_compat.py @@ -1,6 +1,5 @@ # SPDX-License-Identifier: MIT -import inspect import platform import sys import threading @@ -46,6 +45,8 @@ class _AnnotationExtractor: __slots__ = ["sig"] def __init__(self, callable): + import inspect # noqa: PLC0415 + try: self.sig = inspect.signature(callable) except (ValueError, TypeError): # inspect failed @@ -55,6 +56,8 @@ def get_first_param_type(self): """ Return the type annotation of the first argument if it's not empty. """ + import inspect # noqa: PLC0415 + if not self.sig: return None @@ -68,6 +71,8 @@ def get_return_type(self): """ Return the return type if it's not empty. """ + import inspect # noqa: PLC0415 + if ( self.sig and self.sig.return_annotation is not inspect.Signature.empty diff --git a/src/attr/_make.py b/src/attr/_make.py index 793bfd89d..11b50f0c0 100644 --- a/src/attr/_make.py +++ b/src/attr/_make.py @@ -6,7 +6,6 @@ import contextlib import copy import enum -import inspect import itertools import linecache import sys @@ -705,6 +704,8 @@ def __init__( if self._has_pre_init: # Check if the pre init method has more arguments than just `self` # We want to pass arguments if pre init expects arguments + import inspect # noqa: PLC0415 + pre_init_func = cls.__attrs_pre_init__ pre_init_signature = inspect.signature(pre_init_func) self._pre_init_has_args = len(pre_init_signature.parameters) > 1 @@ -920,6 +921,8 @@ def _create_slots_class(self): # To know to update them. additional_closure_functions_to_update = [] if cached_properties: + import inspect # noqa: PLC0415 + class_annotations = _get_annotations(self._cls) for name, func in cached_properties.items(): # Add cached properties to names for slotting. diff --git a/src/attrs/__init__.py b/src/attrs/__init__.py index dc1ce4b97..66e740aad 100644 --- a/src/attrs/__init__.py +++ b/src/attrs/__init__.py @@ -25,7 +25,7 @@ from attr._make import ClassProps from attr._next_gen import asdict, astuple, inspect -from . import converters, exceptions, filters, setters, validators +from . import exceptions, filters, setters __all__ = [ From 5660ef33bbf9623f30c2761c8a9e638d75d39881 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 07:05:50 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/attr/_compat.py | 6 +++--- src/attr/_make.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/attr/_compat.py b/src/attr/_compat.py index 486a1ab3a..e25a4a87a 100644 --- a/src/attr/_compat.py +++ b/src/attr/_compat.py @@ -45,7 +45,7 @@ class _AnnotationExtractor: __slots__ = ["sig"] def __init__(self, callable): - import inspect # noqa: PLC0415 + import inspect try: self.sig = inspect.signature(callable) @@ -56,7 +56,7 @@ def get_first_param_type(self): """ Return the type annotation of the first argument if it's not empty. """ - import inspect # noqa: PLC0415 + import inspect if not self.sig: return None @@ -71,7 +71,7 @@ def get_return_type(self): """ Return the return type if it's not empty. """ - import inspect # noqa: PLC0415 + import inspect if ( self.sig diff --git a/src/attr/_make.py b/src/attr/_make.py index 11b50f0c0..89852f351 100644 --- a/src/attr/_make.py +++ b/src/attr/_make.py @@ -704,7 +704,7 @@ def __init__( if self._has_pre_init: # Check if the pre init method has more arguments than just `self` # We want to pass arguments if pre init expects arguments - import inspect # noqa: PLC0415 + import inspect pre_init_func = cls.__attrs_pre_init__ pre_init_signature = inspect.signature(pre_init_func) @@ -921,7 +921,7 @@ def _create_slots_class(self): # To know to update them. additional_closure_functions_to_update = [] if cached_properties: - import inspect # noqa: PLC0415 + import inspect class_annotations = _get_annotations(self._cls) for name, func in cached_properties.items():