Python from future import annotations 详解:延迟求值与前向引用

它解决什么问题

Python 在解析函数签名时会立即对类型注解求值。如果类还没定义完就引用了自身,会抛 NameError

class Node:
    def __init__(self, next: Node = None):  # NameError: name 'Node' is not defined
        self.next = next

加上这一行就能解决:

from __future__ import annotations

class Node:
    def __init__(self, next: Node = None):  # 正常
        self.next = next

原理是让 Python 把所有注解保存为字符串,不再立即解析。

核心效果

from __future__ import annotations

class User:
    pass

def get_user() -> User:
    return User()

print(get_user.__annotations__)
# {'return': 'User'}  ← 字符串,不是 <class '__main__.User'>

没有这行时,__annotations__ 里存的是实际类对象;有了这行,全部变成字符串,运行时不解析。

解决循环引用

from __future__ import annotations

class A:
    def test(self, b: B) -> None:
        pass

class B:
    def test(self, a: A) -> None:
        pass

不加的话,定义 AB 还不存在,报 NameError

常见使用场景

FastAPI / Pydantic 模型互相引用:

from __future__ import annotations
from pydantic import BaseModel

class Article:
    author: Author

class Author:
    articles: list[Article]

SQLAlchemy ORM 关系:

from __future__ import annotations
from sqlalchemy.orm import Mapped, relationship

class User(Base):
    posts: Mapped[list[Post]] = relationship()

class Post(Base):
    user: Mapped[User] = relationship()

这是 FastAPI、Pydantic、SQLAlchemy 等库的文件开头几乎都有这行的原因。

运行时获取实际类型

如果需要在运行时把字符串注解解析回真实类型,用 typing.get_type_hints()

from __future__ import annotations
import typing

class Foo:
    def bar(self) -> Foo:
        return self

hints = typing.get_type_hints(Foo.bar)
print(hints)
# {'return': <class '__main__.Foo'>}

get_type_hints() 会在运行时把字符串注解解析成真实类型。

Python 版本说明

  • from __future__ import annotations 来自 PEP 563,Python 3.7 引入
  • PEP 563 原本计划在 Python 3.10 成为默认行为,后来推迟,至今(Python 3.12+)仍需显式导入
  • Python 3.10 引入了 X | Y 联合类型语法,可以写 int | None 代替 Optional[int],但前向引用问题 from __future__ import annotations 仍是最简洁的解法

一行导入,让注解写法更自由,是现代 Python 项目的常见约定。