You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/posts/2025/2025-02-01-python-type-hints.md
+93-5Lines changed: 93 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -231,12 +231,45 @@ class C2:
231
231
232
232
```
233
233
234
-
## Postponed Evaluation of Annotations
234
+
## Forward references
235
235
236
-
[PEP 563 (Postponed Evaluation of Annotations)](https://peps.python.org/pep-0563/) (also known as Future annotations import) allows you to use `from __future__ import annotations` to defer evaluation of type annotations until they're actually needed. Generally speaking, it turns every annotation into a string. This helps with:
236
+
```python title="Forward reference (in type annotations)"
237
+
deffoo(x: MyType): # MyType not yet defined
238
+
...
239
+
classMyType:
240
+
...
241
+
242
+
# NameError: name 'MyType' is not defined
243
+
```
244
+
245
+
See [how Pydantic solves the forward reference problem](https://docs.pydantic.dev/latest/concepts/forward_annotations/).
246
+
247
+
## Circular references (import cycles)
248
+
249
+
See below [resolve import cycles](#resolve-import-cycles-by-pep-563) section for more details.
250
+
251
+
```python title="Circular reference (in object or module relationships)"
See [how Pydantic solves the circular reference problem](https://docs.pydantic.dev/latest/concepts/forward_annotations/#cyclic-references).
263
+
264
+
!!! note "A circular reference is a two-way (or multi-way) chain of forward references."
265
+
266
+
## PEP-563 Postponed Evaluation of Annotations
267
+
268
+
[PEP 563 (Postponed Evaluation of Annotations)](https://peps.python.org/pep-0563/) (also known as Future annotations import or stringized annotations) allows you to use `from __future__ import annotations` to defer evaluation of type annotations until they're actually needed.
269
+
Generally speaking, it turns every annotation into a string. This helps with:
`from __future__ import annotations`**must be the first executable line** in the file. You can only have shebang and comment lines before it.
@@ -259,14 +292,69 @@ user = User(name="Alice", age=30, friends=[])
259
292
Future annotations import [doesn't support Python3.10 new syntax for union type](https://mypy.readthedocs.io/en/stable/runtime_troubles.html#using-x-y-syntax-for-unions) (e.g., `int | str`), and it also doesn't support the new syntax for type variables with upper bounds (e.g., `type[C]`), neither for some dynamic evaluation of annotations.
260
293
So it's preferable **NOT TO USE**`from __future__ import annotation` as much as possible, just use `string literal annotations` for forward references and circular imports.
261
294
262
-
## Import cycles
295
+
## PEP-649 Deferred Evaluation of Annotations Using Descriptors
296
+
297
+
[PEP-563 (stringized annotations)](#pep-563-postponed-evaluation-of-annotations) solved the forward-reference and circular-reference problems for static type analysis users, and also fostered intriguing new uses for annotation metadata. But stringized annotations in turn caused chronic ==problems for runtime users of annotations==. (See why [PEP-563 is pain for runtime users like Pydantic](https://docs.pydantic.dev/latest/internals/resolving_annotations/).).
298
+
299
+
PEP-649 (String Literal Annotations) is proposed to be added in Python 3.14, it adds a new internal mechanism for ==lazily computing annotations on demand==, via a new object method called `__annotate__`.
300
+
`self.__annotate__()` is called the first time (so called **lazy**) `self.__annotations__` attribute is accessed, and return value is stored in `self.__annotations__` and the result is cached for future accesses.
301
+
This allows annotations to be computed only when needed, and also allows them to be computed in a way that can handle forward references and circular references.
302
+
303
+
A high-level overview of the mechanism is as follows:
# __annotations__ on a function object is already a
309
+
# "data descriptor" in Python, we're just changing
310
+
# what it does
311
+
@property
312
+
def__annotations__(self):
313
+
returnself.__annotate__()
314
+
315
+
# ...
316
+
317
+
defannotate_foo():
318
+
return {'x': int, 'y': MyType, 'return': float}
319
+
320
+
deffoo(x=3, y="abc"):
321
+
...
322
+
323
+
foo.__annotate__ = annotate_foo
324
+
325
+
classMyType:
326
+
...
327
+
328
+
foo_y_annotation = foo.__annotations__['y']
329
+
```
330
+
331
+
> The important change is that the code constructing the annotations dict now lives in a function here, called `annotate_foo()`. But this function isn't called until we ask for the value of `foo.__annotations__`, and we don't do that until after the definition of `MyType`. So this code also runs successfully, and `foo_y_annotation` now has the correct value. The class `MyType` even though `MyType` wasn't defined until after the annotation was defined.
332
+
333
+
!!! note "The basic idea of PEP-649 was briefly discussed and rejected during the early-days of PEP-563 discussion."
0 commit comments