https://github.com/python/cpython/commit/305be5fb1a1ece7f9651ae98053dbe79bf439aa4
commit: 305be5fb1a1ece7f9651ae98053dbe79bf439aa4
branch: main
author: Jelle Zijlstra <[email protected]>
committer: JelleZijlstra <[email protected]>
date: 2025-04-04T08:57:10-07:00
summary:
gh-118761: Lazily import annotationlib in typing (#132060)
annotationlib is used quite a few times in typing.py, but I think the
usages are just rare enough that this makes sense.
The import would get triggered by:
- Using get_type_hints(), evaluate_forward_ref(), and similar introspection
functions
- Using a string annotation anywhere that goes through _type_convert (e.g.,
"Final['x']" will trigger an annotationlib import in order to access the
ForwardRef class).
- Creating a TypedDict or NamedTuple (unless it's empty or PEP 563 is on).
Lots of programs will want to use typing without any of these, so the tradeoff
seems worth it.
files:
M Lib/typing.py
diff --git a/Lib/typing.py b/Lib/typing.py
index 6536a97f6e1910..e67284fc571b1b 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -19,8 +19,6 @@
"""
from abc import abstractmethod, ABCMeta
-import annotationlib
-from annotationlib import ForwardRef
import collections
from collections import defaultdict
import collections.abc
@@ -163,6 +161,15 @@
'Unpack',
]
+class _LazyAnnotationLib:
+ def __getattr__(self, attr):
+ global _lazy_annotationlib
+ import annotationlib
+ _lazy_annotationlib = annotationlib
+ return getattr(annotationlib, attr)
+
+_lazy_annotationlib = _LazyAnnotationLib()
+
def _type_convert(arg, module=None, *, allow_special_forms=False):
"""For converting None to type(None), and strings to ForwardRef."""
@@ -246,7 +253,7 @@ def _type_repr(obj):
if isinstance(obj, tuple):
# Special case for `repr` of types with `ParamSpec`:
return '[' + ', '.join(_type_repr(t) for t in obj) + ']'
- return annotationlib.value_to_string(obj)
+ return _lazy_annotationlib.value_to_string(obj)
def _collect_type_parameters(args, *, enforce_default_ordering: bool = True):
@@ -423,7 +430,7 @@ def __repr__(self):
def _eval_type(t, globalns, localns, type_params=_sentinel, *,
recursive_guard=frozenset(),
- format=annotationlib.Format.VALUE, owner=None):
+ format=None, owner=None):
"""Evaluate all forward references in the given type t.
For use of globalns and localns see the docstring for get_type_hints().
@@ -433,7 +440,7 @@ def _eval_type(t, globalns, localns, type_params=_sentinel,
*, recursive_guard=f
if type_params is _sentinel:
_deprecation_warning_for_no_type_params_passed("typing._eval_type")
type_params = ()
- if isinstance(t, ForwardRef):
+ if isinstance(t, _lazy_annotationlib.ForwardRef):
# If the forward_ref has __forward_module__ set, evaluate() infers the
globals
# from the module, and it will probably pick better than the globals
we have here.
if t.__forward_module__ is not None:
@@ -930,7 +937,7 @@ def run(arg: Child | Unrelated):
def _make_forward_ref(code, **kwargs):
- forward_ref = ForwardRef(code, **kwargs)
+ forward_ref = _lazy_annotationlib.ForwardRef(code, **kwargs)
# For compatibility, eagerly compile the forwardref's code.
forward_ref.__forward_code__
return forward_ref
@@ -943,7 +950,7 @@ def evaluate_forward_ref(
globals=None,
locals=None,
type_params=None,
- format=annotationlib.Format.VALUE,
+ format=None,
_recursive_guard=frozenset(),
):
"""Evaluate a forward reference as a type hint.
@@ -965,10 +972,11 @@ def evaluate_forward_ref(
evaluating the forward reference. This parameter should be provided (though
it may be an empty tuple) if *owner* is not given and the forward reference
does not already have an owner set. *format* specifies the format of the
- annotation and is a member of the annotationlib.Format enum.
+ annotation and is a member of the annotationlib.Format enum, defaulting to
+ VALUE.
"""
- if format == annotationlib.Format.STRING:
+ if format == _lazy_annotationlib.Format.STRING:
return forward_ref.__forward_arg__
if forward_ref.__forward_arg__ in _recursive_guard:
return forward_ref
@@ -977,7 +985,7 @@ def evaluate_forward_ref(
value = forward_ref.evaluate(globals=globals, locals=locals,
type_params=type_params, owner=owner)
except NameError:
- if format == annotationlib.Format.FORWARDREF:
+ if format == _lazy_annotationlib.Format.FORWARDREF:
return forward_ref
else:
raise
@@ -2257,7 +2265,7 @@ def greet(name: str) -> None:
def get_type_hints(obj, globalns=None, localns=None, include_extras=False,
- *, format=annotationlib.Format.VALUE):
+ *, format=None):
"""Return type hints for an object.
This is often the same as obj.__annotations__, but it handles
@@ -2290,12 +2298,15 @@ def get_type_hints(obj, globalns=None, localns=None,
include_extras=False,
"""
if getattr(obj, '__no_type_check__', None):
return {}
+ Format = _lazy_annotationlib.Format
+ if format is None:
+ format = Format.VALUE
# Classes require a special treatment.
if isinstance(obj, type):
hints = {}
for base in reversed(obj.__mro__):
- ann = annotationlib.get_annotations(base, format=format)
- if format == annotationlib.Format.STRING:
+ ann = _lazy_annotationlib.get_annotations(base, format=format)
+ if format == Format.STRING:
hints.update(ann)
continue
if globalns is None:
@@ -2319,12 +2330,12 @@ def get_type_hints(obj, globalns=None, localns=None,
include_extras=False,
value = _eval_type(value, base_globals, base_locals,
base.__type_params__,
format=format, owner=obj)
hints[name] = value
- if include_extras or format == annotationlib.Format.STRING:
+ if include_extras or format == Format.STRING:
return hints
else:
return {k: _strip_annotations(t) for k, t in hints.items()}
- hints = annotationlib.get_annotations(obj, format=format)
+ hints = _lazy_annotationlib.get_annotations(obj, format=format)
if (
not hints
and not isinstance(obj, types.ModuleType)
@@ -2333,7 +2344,7 @@ def get_type_hints(obj, globalns=None, localns=None,
include_extras=False,
and not hasattr(obj, '__annotate__')
):
raise TypeError(f"{obj!r} is not a module, class, or callable.")
- if format == annotationlib.Format.STRING:
+ if format == Format.STRING:
return hints
if globalns is None:
@@ -2850,10 +2861,10 @@ def _make_eager_annotate(types):
for key, val in types.items()}
def annotate(format):
match format:
- case annotationlib.Format.VALUE | annotationlib.Format.FORWARDREF:
+ case _lazy_annotationlib.Format.VALUE |
_lazy_annotationlib.Format.FORWARDREF:
return checked_types
- case annotationlib.Format.STRING:
- return annotationlib.annotations_to_string(types)
+ case _lazy_annotationlib.Format.STRING:
+ return _lazy_annotationlib.annotations_to_string(types)
case _:
raise NotImplementedError(format)
return annotate
@@ -2884,7 +2895,8 @@ def __new__(cls, typename, bases, ns):
annotate = _make_eager_annotate(types)
elif "__annotate__" in ns:
original_annotate = ns["__annotate__"]
- types = annotationlib.call_annotate_function(original_annotate,
annotationlib.Format.FORWARDREF)
+ types = _lazy_annotationlib.call_annotate_function(
+ original_annotate, _lazy_annotationlib.Format.FORWARDREF)
field_names = list(types)
# For backward compatibility, type-check all the types at creation
time
@@ -2892,8 +2904,9 @@ def __new__(cls, typename, bases, ns):
_type_check(typ, "field annotation must be a type")
def annotate(format):
- annos =
annotationlib.call_annotate_function(original_annotate, format)
- if format != annotationlib.Format.STRING:
+ annos = _lazy_annotationlib.call_annotate_function(
+ original_annotate, format)
+ if format != _lazy_annotationlib.Format.STRING:
return {key: _type_check(val, f"field {key} annotation
must be a type")
for key, val in annos.items()}
return annos
@@ -3069,8 +3082,8 @@ def __new__(cls, name, bases, ns, total=True):
own_annotations = ns["__annotations__"]
elif "__annotate__" in ns:
own_annotate = ns["__annotate__"]
- own_annotations = annotationlib.call_annotate_function(
- own_annotate, annotationlib.Format.FORWARDREF, owner=tp_dict
+ own_annotations = _lazy_annotationlib.call_annotate_function(
+ own_annotate, _lazy_annotationlib.Format.FORWARDREF,
owner=tp_dict
)
else:
own_annotate = None
@@ -3137,18 +3150,20 @@ def __annotate__(format):
base_annotate = base.__annotate__
if base_annotate is None:
continue
- base_annos =
annotationlib.call_annotate_function(base.__annotate__, format, owner=base)
+ base_annos = _lazy_annotationlib.call_annotate_function(
+ base.__annotate__, format, owner=base)
annos.update(base_annos)
if own_annotate is not None:
- own = annotationlib.call_annotate_function(own_annotate,
format, owner=tp_dict)
- if format != annotationlib.Format.STRING:
+ own = _lazy_annotationlib.call_annotate_function(
+ own_annotate, format, owner=tp_dict)
+ if format != _lazy_annotationlib.Format.STRING:
own = {
n: _type_check(tp, msg, module=tp_dict.__module__)
for n, tp in own.items()
}
- elif format == annotationlib.Format.STRING:
- own = annotationlib.annotations_to_string(own_annotations)
- elif format in (annotationlib.Format.FORWARDREF,
annotationlib.Format.VALUE):
+ elif format == _lazy_annotationlib.Format.STRING:
+ own =
_lazy_annotationlib.annotations_to_string(own_annotations)
+ elif format in (_lazy_annotationlib.Format.FORWARDREF,
_lazy_annotationlib.Format.VALUE):
own = own_checked_annotations
else:
raise NotImplementedError(format)
@@ -3732,7 +3747,9 @@ def __getattr__(attr):
Soft-deprecated objects which are costly to create
are only created on-demand here.
"""
- if attr in {"Pattern", "Match"}:
+ if attr == "ForwardRef":
+ obj = _lazy_annotationlib.ForwardRef
+ elif attr in {"Pattern", "Match"}:
import re
obj = _alias(getattr(re, attr), 1)
elif attr in {"ContextManager", "AsyncContextManager"}:
_______________________________________________
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]