Skip to content

Commit 305be5f

Browse files
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.
1 parent 04bc681 commit 305be5f

File tree

1 file changed

+48
-31
lines changed

1 file changed

+48
-31
lines changed

Lib/typing.py

+48-31
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@
1919
"""
2020

2121
from abc import abstractmethod, ABCMeta
22-
import annotationlib
23-
from annotationlib import ForwardRef
2422
import collections
2523
from collections import defaultdict
2624
import collections.abc
@@ -163,6 +161,15 @@
163161
'Unpack',
164162
]
165163

164+
class _LazyAnnotationLib:
165+
def __getattr__(self, attr):
166+
global _lazy_annotationlib
167+
import annotationlib
168+
_lazy_annotationlib = annotationlib
169+
return getattr(annotationlib, attr)
170+
171+
_lazy_annotationlib = _LazyAnnotationLib()
172+
166173

167174
def _type_convert(arg, module=None, *, allow_special_forms=False):
168175
"""For converting None to type(None), and strings to ForwardRef."""
@@ -246,7 +253,7 @@ def _type_repr(obj):
246253
if isinstance(obj, tuple):
247254
# Special case for `repr` of types with `ParamSpec`:
248255
return '[' + ', '.join(_type_repr(t) for t in obj) + ']'
249-
return annotationlib.value_to_string(obj)
256+
return _lazy_annotationlib.value_to_string(obj)
250257

251258

252259
def _collect_type_parameters(args, *, enforce_default_ordering: bool = True):
@@ -423,7 +430,7 @@ def __repr__(self):
423430

424431

425432
def _eval_type(t, globalns, localns, type_params=_sentinel, *, recursive_guard=frozenset(),
426-
format=annotationlib.Format.VALUE, owner=None):
433+
format=None, owner=None):
427434
"""Evaluate all forward references in the given type t.
428435
429436
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
433440
if type_params is _sentinel:
434441
_deprecation_warning_for_no_type_params_passed("typing._eval_type")
435442
type_params = ()
436-
if isinstance(t, ForwardRef):
443+
if isinstance(t, _lazy_annotationlib.ForwardRef):
437444
# If the forward_ref has __forward_module__ set, evaluate() infers the globals
438445
# from the module, and it will probably pick better than the globals we have here.
439446
if t.__forward_module__ is not None:
@@ -930,7 +937,7 @@ def run(arg: Child | Unrelated):
930937

931938

932939
def _make_forward_ref(code, **kwargs):
933-
forward_ref = ForwardRef(code, **kwargs)
940+
forward_ref = _lazy_annotationlib.ForwardRef(code, **kwargs)
934941
# For compatibility, eagerly compile the forwardref's code.
935942
forward_ref.__forward_code__
936943
return forward_ref
@@ -943,7 +950,7 @@ def evaluate_forward_ref(
943950
globals=None,
944951
locals=None,
945952
type_params=None,
946-
format=annotationlib.Format.VALUE,
953+
format=None,
947954
_recursive_guard=frozenset(),
948955
):
949956
"""Evaluate a forward reference as a type hint.
@@ -965,10 +972,11 @@ def evaluate_forward_ref(
965972
evaluating the forward reference. This parameter should be provided (though
966973
it may be an empty tuple) if *owner* is not given and the forward reference
967974
does not already have an owner set. *format* specifies the format of the
968-
annotation and is a member of the annotationlib.Format enum.
975+
annotation and is a member of the annotationlib.Format enum, defaulting to
976+
VALUE.
969977
970978
"""
971-
if format == annotationlib.Format.STRING:
979+
if format == _lazy_annotationlib.Format.STRING:
972980
return forward_ref.__forward_arg__
973981
if forward_ref.__forward_arg__ in _recursive_guard:
974982
return forward_ref
@@ -977,7 +985,7 @@ def evaluate_forward_ref(
977985
value = forward_ref.evaluate(globals=globals, locals=locals,
978986
type_params=type_params, owner=owner)
979987
except NameError:
980-
if format == annotationlib.Format.FORWARDREF:
988+
if format == _lazy_annotationlib.Format.FORWARDREF:
981989
return forward_ref
982990
else:
983991
raise
@@ -2257,7 +2265,7 @@ def greet(name: str) -> None:
22572265

22582266

22592267
def get_type_hints(obj, globalns=None, localns=None, include_extras=False,
2260-
*, format=annotationlib.Format.VALUE):
2268+
*, format=None):
22612269
"""Return type hints for an object.
22622270
22632271
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,
22902298
"""
22912299
if getattr(obj, '__no_type_check__', None):
22922300
return {}
2301+
Format = _lazy_annotationlib.Format
2302+
if format is None:
2303+
format = Format.VALUE
22932304
# Classes require a special treatment.
22942305
if isinstance(obj, type):
22952306
hints = {}
22962307
for base in reversed(obj.__mro__):
2297-
ann = annotationlib.get_annotations(base, format=format)
2298-
if format == annotationlib.Format.STRING:
2308+
ann = _lazy_annotationlib.get_annotations(base, format=format)
2309+
if format == Format.STRING:
22992310
hints.update(ann)
23002311
continue
23012312
if globalns is None:
@@ -2319,12 +2330,12 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False,
23192330
value = _eval_type(value, base_globals, base_locals, base.__type_params__,
23202331
format=format, owner=obj)
23212332
hints[name] = value
2322-
if include_extras or format == annotationlib.Format.STRING:
2333+
if include_extras or format == Format.STRING:
23232334
return hints
23242335
else:
23252336
return {k: _strip_annotations(t) for k, t in hints.items()}
23262337

2327-
hints = annotationlib.get_annotations(obj, format=format)
2338+
hints = _lazy_annotationlib.get_annotations(obj, format=format)
23282339
if (
23292340
not hints
23302341
and not isinstance(obj, types.ModuleType)
@@ -2333,7 +2344,7 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False,
23332344
and not hasattr(obj, '__annotate__')
23342345
):
23352346
raise TypeError(f"{obj!r} is not a module, class, or callable.")
2336-
if format == annotationlib.Format.STRING:
2347+
if format == Format.STRING:
23372348
return hints
23382349

23392350
if globalns is None:
@@ -2850,10 +2861,10 @@ def _make_eager_annotate(types):
28502861
for key, val in types.items()}
28512862
def annotate(format):
28522863
match format:
2853-
case annotationlib.Format.VALUE | annotationlib.Format.FORWARDREF:
2864+
case _lazy_annotationlib.Format.VALUE | _lazy_annotationlib.Format.FORWARDREF:
28542865
return checked_types
2855-
case annotationlib.Format.STRING:
2856-
return annotationlib.annotations_to_string(types)
2866+
case _lazy_annotationlib.Format.STRING:
2867+
return _lazy_annotationlib.annotations_to_string(types)
28572868
case _:
28582869
raise NotImplementedError(format)
28592870
return annotate
@@ -2884,16 +2895,18 @@ def __new__(cls, typename, bases, ns):
28842895
annotate = _make_eager_annotate(types)
28852896
elif "__annotate__" in ns:
28862897
original_annotate = ns["__annotate__"]
2887-
types = annotationlib.call_annotate_function(original_annotate, annotationlib.Format.FORWARDREF)
2898+
types = _lazy_annotationlib.call_annotate_function(
2899+
original_annotate, _lazy_annotationlib.Format.FORWARDREF)
28882900
field_names = list(types)
28892901

28902902
# For backward compatibility, type-check all the types at creation time
28912903
for typ in types.values():
28922904
_type_check(typ, "field annotation must be a type")
28932905

28942906
def annotate(format):
2895-
annos = annotationlib.call_annotate_function(original_annotate, format)
2896-
if format != annotationlib.Format.STRING:
2907+
annos = _lazy_annotationlib.call_annotate_function(
2908+
original_annotate, format)
2909+
if format != _lazy_annotationlib.Format.STRING:
28972910
return {key: _type_check(val, f"field {key} annotation must be a type")
28982911
for key, val in annos.items()}
28992912
return annos
@@ -3069,8 +3082,8 @@ def __new__(cls, name, bases, ns, total=True):
30693082
own_annotations = ns["__annotations__"]
30703083
elif "__annotate__" in ns:
30713084
own_annotate = ns["__annotate__"]
3072-
own_annotations = annotationlib.call_annotate_function(
3073-
own_annotate, annotationlib.Format.FORWARDREF, owner=tp_dict
3085+
own_annotations = _lazy_annotationlib.call_annotate_function(
3086+
own_annotate, _lazy_annotationlib.Format.FORWARDREF, owner=tp_dict
30743087
)
30753088
else:
30763089
own_annotate = None
@@ -3137,18 +3150,20 @@ def __annotate__(format):
31373150
base_annotate = base.__annotate__
31383151
if base_annotate is None:
31393152
continue
3140-
base_annos = annotationlib.call_annotate_function(base.__annotate__, format, owner=base)
3153+
base_annos = _lazy_annotationlib.call_annotate_function(
3154+
base.__annotate__, format, owner=base)
31413155
annos.update(base_annos)
31423156
if own_annotate is not None:
3143-
own = annotationlib.call_annotate_function(own_annotate, format, owner=tp_dict)
3144-
if format != annotationlib.Format.STRING:
3157+
own = _lazy_annotationlib.call_annotate_function(
3158+
own_annotate, format, owner=tp_dict)
3159+
if format != _lazy_annotationlib.Format.STRING:
31453160
own = {
31463161
n: _type_check(tp, msg, module=tp_dict.__module__)
31473162
for n, tp in own.items()
31483163
}
3149-
elif format == annotationlib.Format.STRING:
3150-
own = annotationlib.annotations_to_string(own_annotations)
3151-
elif format in (annotationlib.Format.FORWARDREF, annotationlib.Format.VALUE):
3164+
elif format == _lazy_annotationlib.Format.STRING:
3165+
own = _lazy_annotationlib.annotations_to_string(own_annotations)
3166+
elif format in (_lazy_annotationlib.Format.FORWARDREF, _lazy_annotationlib.Format.VALUE):
31523167
own = own_checked_annotations
31533168
else:
31543169
raise NotImplementedError(format)
@@ -3732,7 +3747,9 @@ def __getattr__(attr):
37323747
Soft-deprecated objects which are costly to create
37333748
are only created on-demand here.
37343749
"""
3735-
if attr in {"Pattern", "Match"}:
3750+
if attr == "ForwardRef":
3751+
obj = _lazy_annotationlib.ForwardRef
3752+
elif attr in {"Pattern", "Match"}:
37363753
import re
37373754
obj = _alias(getattr(re, attr), 1)
37383755
elif attr in {"ContextManager", "AsyncContextManager"}:

0 commit comments

Comments
 (0)