Skip to content

Commit e22f2e7

Browse files
committed
pythongh-118761: Lazily import annotationlib in typing
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 0dbaeb9 commit e22f2e7

File tree

1 file changed

+55
-31
lines changed

1 file changed

+55
-31
lines changed

Diff for: Lib/typing.py

+55-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
@@ -164,6 +162,22 @@
164162
'Unpack',
165163
]
166164

165+
class _LazyAnnotationLib:
166+
def __init__(self) -> None:
167+
self._annolib = None
168+
169+
def __getattr__(self, attr):
170+
if self._annolib is None:
171+
import annotationlib
172+
173+
self._annolib = annotationlib
174+
175+
value = getattr(self._annolib, attr)
176+
setattr(self, attr, value)
177+
return value
178+
179+
_lazy_annotationlib = _LazyAnnotationLib()
180+
167181

168182
def _type_convert(arg, module=None, *, allow_special_forms=False):
169183
"""For converting None to type(None), and strings to ForwardRef."""
@@ -247,7 +261,7 @@ def _type_repr(obj):
247261
if isinstance(obj, tuple):
248262
# Special case for `repr` of types with `ParamSpec`:
249263
return '[' + ', '.join(_type_repr(t) for t in obj) + ']'
250-
return annotationlib.value_to_string(obj)
264+
return _lazy_annotationlib.value_to_string(obj)
251265

252266

253267
def _collect_type_parameters(args, *, enforce_default_ordering: bool = True):
@@ -424,7 +438,7 @@ def __repr__(self):
424438

425439

426440
def _eval_type(t, globalns, localns, type_params=_sentinel, *, recursive_guard=frozenset(),
427-
format=annotationlib.Format.VALUE, owner=None):
441+
format=None, owner=None):
428442
"""Evaluate all forward references in the given type t.
429443
430444
For use of globalns and localns see the docstring for get_type_hints().
@@ -434,7 +448,7 @@ def _eval_type(t, globalns, localns, type_params=_sentinel, *, recursive_guard=f
434448
if type_params is _sentinel:
435449
_deprecation_warning_for_no_type_params_passed("typing._eval_type")
436450
type_params = ()
437-
if isinstance(t, ForwardRef):
451+
if isinstance(t, _lazy_annotationlib.ForwardRef):
438452
# If the forward_ref has __forward_module__ set, evaluate() infers the globals
439453
# from the module, and it will probably pick better than the globals we have here.
440454
if t.__forward_module__ is not None:
@@ -931,7 +945,7 @@ def run(arg: Child | Unrelated):
931945

932946

933947
def _make_forward_ref(code, **kwargs):
934-
forward_ref = ForwardRef(code, **kwargs)
948+
forward_ref = _lazy_annotationlib.ForwardRef(code, **kwargs)
935949
# For compatibility, eagerly compile the forwardref's code.
936950
forward_ref.__forward_code__
937951
return forward_ref
@@ -944,7 +958,7 @@ def evaluate_forward_ref(
944958
globals=None,
945959
locals=None,
946960
type_params=None,
947-
format=annotationlib.Format.VALUE,
961+
format=None,
948962
_recursive_guard=frozenset(),
949963
):
950964
"""Evaluate a forward reference as a type hint.
@@ -966,10 +980,11 @@ def evaluate_forward_ref(
966980
evaluating the forward reference. This parameter should be provided (though
967981
it may be an empty tuple) if *owner* is not given and the forward reference
968982
does not already have an owner set. *format* specifies the format of the
969-
annotation and is a member of the annotationlib.Format enum.
983+
annotation and is a member of the annotationlib.Format enum, defaulting to
984+
VALUE.
970985
971986
"""
972-
if format == annotationlib.Format.STRING:
987+
if format == _lazy_annotationlib.Format.STRING:
973988
return forward_ref.__forward_arg__
974989
if forward_ref.__forward_arg__ in _recursive_guard:
975990
return forward_ref
@@ -978,7 +993,7 @@ def evaluate_forward_ref(
978993
value = forward_ref.evaluate(globals=globals, locals=locals,
979994
type_params=type_params, owner=owner)
980995
except NameError:
981-
if format == annotationlib.Format.FORWARDREF:
996+
if format == _lazy_annotationlib.Format.FORWARDREF:
982997
return forward_ref
983998
else:
984999
raise
@@ -2254,7 +2269,7 @@ def greet(name: str) -> None:
22542269

22552270

22562271
def get_type_hints(obj, globalns=None, localns=None, include_extras=False,
2257-
*, format=annotationlib.Format.VALUE):
2272+
*, format=None):
22582273
"""Return type hints for an object.
22592274
22602275
This is often the same as obj.__annotations__, but it handles
@@ -2287,12 +2302,15 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False,
22872302
"""
22882303
if getattr(obj, '__no_type_check__', None):
22892304
return {}
2305+
Format = _lazy_annotationlib.Format
2306+
if format is None:
2307+
format = Format.VALUE
22902308
# Classes require a special treatment.
22912309
if isinstance(obj, type):
22922310
hints = {}
22932311
for base in reversed(obj.__mro__):
2294-
ann = annotationlib.get_annotations(base, format=format)
2295-
if format == annotationlib.Format.STRING:
2312+
ann = _lazy_annotationlib.get_annotations(base, format=format)
2313+
if format == Format.STRING:
22962314
hints.update(ann)
22972315
continue
22982316
if globalns is None:
@@ -2316,12 +2334,12 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False,
23162334
value = _eval_type(value, base_globals, base_locals, base.__type_params__,
23172335
format=format, owner=obj)
23182336
hints[name] = value
2319-
if include_extras or format == annotationlib.Format.STRING:
2337+
if include_extras or format == Format.STRING:
23202338
return hints
23212339
else:
23222340
return {k: _strip_annotations(t) for k, t in hints.items()}
23232341

2324-
hints = annotationlib.get_annotations(obj, format=format)
2342+
hints = _lazy_annotationlib.get_annotations(obj, format=format)
23252343
if (
23262344
not hints
23272345
and not isinstance(obj, types.ModuleType)
@@ -2330,7 +2348,7 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False,
23302348
and not hasattr(obj, '__annotate__')
23312349
):
23322350
raise TypeError(f"{obj!r} is not a module, class, or callable.")
2333-
if format == annotationlib.Format.STRING:
2351+
if format == Format.STRING:
23342352
return hints
23352353

23362354
if globalns is None:
@@ -2847,10 +2865,10 @@ def _make_eager_annotate(types):
28472865
for key, val in types.items()}
28482866
def annotate(format):
28492867
match format:
2850-
case annotationlib.Format.VALUE | annotationlib.Format.FORWARDREF:
2868+
case _lazy_annotationlib.Format.VALUE | _lazy_annotationlib.Format.FORWARDREF:
28512869
return checked_types
2852-
case annotationlib.Format.STRING:
2853-
return annotationlib.annotations_to_string(types)
2870+
case _lazy_annotationlib.Format.STRING:
2871+
return _lazy_annotationlib.annotations_to_string(types)
28542872
case _:
28552873
raise NotImplementedError(format)
28562874
return annotate
@@ -2881,16 +2899,18 @@ def __new__(cls, typename, bases, ns):
28812899
annotate = _make_eager_annotate(types)
28822900
elif "__annotate__" in ns:
28832901
original_annotate = ns["__annotate__"]
2884-
types = annotationlib.call_annotate_function(original_annotate, annotationlib.Format.FORWARDREF)
2902+
types = _lazy_annotationlib.call_annotate_function(
2903+
original_annotate, _lazy_annotationlib.Format.FORWARDREF)
28852904
field_names = list(types)
28862905

28872906
# For backward compatibility, type-check all the types at creation time
28882907
for typ in types.values():
28892908
_type_check(typ, "field annotation must be a type")
28902909

28912910
def annotate(format):
2892-
annos = annotationlib.call_annotate_function(original_annotate, format)
2893-
if format != annotationlib.Format.STRING:
2911+
annos = _lazy_annotationlib.call_annotate_function(
2912+
original_annotate, format)
2913+
if format != _lazy_annotationlib.Format.STRING:
28942914
return {key: _type_check(val, f"field {key} annotation must be a type")
28952915
for key, val in annos.items()}
28962916
return annos
@@ -3066,8 +3086,8 @@ def __new__(cls, name, bases, ns, total=True):
30663086
own_annotations = ns["__annotations__"]
30673087
elif "__annotate__" in ns:
30683088
own_annotate = ns["__annotate__"]
3069-
own_annotations = annotationlib.call_annotate_function(
3070-
own_annotate, annotationlib.Format.FORWARDREF, owner=tp_dict
3089+
own_annotations = _lazy_annotationlib.call_annotate_function(
3090+
own_annotate, _lazy_annotationlib.Format.FORWARDREF, owner=tp_dict
30713091
)
30723092
else:
30733093
own_annotate = None
@@ -3134,18 +3154,20 @@ def __annotate__(format):
31343154
base_annotate = base.__annotate__
31353155
if base_annotate is None:
31363156
continue
3137-
base_annos = annotationlib.call_annotate_function(base.__annotate__, format, owner=base)
3157+
base_annos = _lazy_annotationlib.call_annotate_function(
3158+
base.__annotate__, format, owner=base)
31383159
annos.update(base_annos)
31393160
if own_annotate is not None:
3140-
own = annotationlib.call_annotate_function(own_annotate, format, owner=tp_dict)
3141-
if format != annotationlib.Format.STRING:
3161+
own = _lazy_annotationlib.call_annotate_function(
3162+
own_annotate, format, owner=tp_dict)
3163+
if format != _lazy_annotationlib.Format.STRING:
31423164
own = {
31433165
n: _type_check(tp, msg, module=tp_dict.__module__)
31443166
for n, tp in own.items()
31453167
}
3146-
elif format == annotationlib.Format.STRING:
3147-
own = annotationlib.annotations_to_string(own_annotations)
3148-
elif format in (annotationlib.Format.FORWARDREF, annotationlib.Format.VALUE):
3168+
elif format == _lazy_annotationlib.Format.STRING:
3169+
own = _lazy_annotationlib.annotations_to_string(own_annotations)
3170+
elif format in (_lazy_annotationlib.Format.FORWARDREF, _lazy_annotationlib.Format.VALUE):
31493171
own = own_checked_annotations
31503172
else:
31513173
raise NotImplementedError(format)
@@ -3729,7 +3751,9 @@ def __getattr__(attr):
37293751
Soft-deprecated objects which are costly to create
37303752
are only created on-demand here.
37313753
"""
3732-
if attr in {"Pattern", "Match"}:
3754+
if attr == "ForwardRef":
3755+
obj = _lazy_annotationlib.ForwardRef
3756+
elif attr in {"Pattern", "Match"}:
37333757
import re
37343758
obj = _alias(getattr(re, attr), 1)
37353759
elif attr in {"ContextManager", "AsyncContextManager"}:

0 commit comments

Comments
 (0)