Python变量与数据类型全解析(最新整理)

前言

题面里「最新整理」四个字是关键------如果只是把变量和基础类型再讲一遍,那和十年前的教程没有区别。真正需要「最新」的,是类型注解这套机制 :从 3.5 引入 typing,到 3.9 支持内置泛型,3.10 支持 X | Y 联合写法,3.12 引入 type 语句和类型参数语法,再到 3.14 把注解的延迟求值变成默认行为(PEP 649 / PEP 749),这条线一直在变。用旧写法写新代码,轻则被类型检查器抱怨,重则踩到求值时机的坑。

最普遍的误解只有一个,但杀伤力很大:以为加了类型注解,Python 就会在运行时帮你检查类型。不会。注解默认只是「给工具看的元数据」,运行时它既不校验也不强制转换。传错类型的实参,程序照跑不误,除非你额外用了某个会在运行时读注解并校验的库。

本文讲三件事:注解到底是什么、现代写法该怎么写、以及 3.14 的延迟求值改变了什么。示例会明确标注每个特性所需的 Python 版本,并给出旧版本的替代写法。

一、注解是什么,不是什么

注解(annotation)是写在冒号后面的表达式,可以作用在变量、函数参数、函数返回值、类属性上。

它不是 :运行时的类型检查、类型转换、接口约束。它是:给类型检查器(mypy、pyright 之类)、IDE、以及文档工具消费的结构化元数据。

python 复制代码
# 适用于 Python 3.8+

def greet(name: str, times: int = 1) -> str:

    return ("你好," + name + "!") * times



print(greet("小明", 2))          # 你好,小明!你好,小明!

print(greet(123))                # 不报错!注解不拦截错误类型

最后一行能跑通,正是「注解不检查」的证据。真正想做运行时校验,得自己写判断,或者用第三方库------但那已经超出标准库范围了。

注解能被程序读到:函数的注解放在 __annotations__ 里,模块级变量的注解放在模块的 __annotations__ 里。

二、typing 模块:复杂类型怎么标注

基础类型直接用内置名字就行:int、str、float、bool、bytes、list、dict。但「列表里装的是字符串」这种信息,光写 list 表达不出来,需要泛型写法。历史上要用 typing 模块里的别名:

场景 老写法(typing) 现代写法 现代写法所需版本

|-------|-------------|-------------|------|
| 字符串列表 | List[str] | list[str] | 3.9+ |

|----|------------------|------------------|------|
| 字典 | Dict[str, int] | dict[str, int] | 3.9+ |

|------|-------------------|-------------------|------|
| 元组定长 | Tuple[int, str] | tuple[int, str] | 3.9+ |

|----|------------|------------|------|
| 集合 | Set[int] | set[int] | 3.9+ |

|------|-------|------------------|------|
| 任意类型 | Any | Any(仍需 typing) | 3.5+ |

|---------|-----------------|----------|--------|
| 可为 None | Optional[int] | `int \ | None` |

|------|-------------------|----------|-------|
| 联合类型 | Union[int, str] | `int \ | str` |

|-----|------------------------|----------------------------------------|------|
| 可调用 | Callable[[int], str] | collections.abc.Callable[[int], str] | 3.9+ |

Optional[int] 只是 Union[int, None] 的简写,不代表参数可以省略 ------「可以省略」要靠默认值表达。Any 表示「放弃检查」,不是「任意类型都安全」,标了 Any 就等于放弃了这一处的类型保障,能少用就少用。

python 复制代码
# 适用于 Python 3.10+

from typing import Any



def total_score(scores: dict[str, int]) -> int:

    return sum(scores.values())



def find_user(uid: int) -> dict[str, Any] | None:

    data = {1: {"name": "小明"}, 2: {"name": "小红"}}

    return data.get(uid)



print(total_score({"语文": 90, "数学": 95}))   # 185

print(find_user(1))

print(find_user(99))                          # None

如果运行环境是 3.9 或更早,dict[str, int] 里只有最外层写法可换成 Dict,| 也要换成 Optional[...]。

三、版本演进:从 3.9 到 3.12

  • 3.9 :内置容器类型支持下标,可以写 list[int]、dict[str, int];同版本还引入了字典合并运算符 |(PEP 584)和 str.removeprefix() / str.removesuffix()。
  • 3.10 :引入了「联合类型运算符」X | Y,以及结构化模式匹配 match / case。
  • 3.12 :引入了 type 语句来声明类型别名,以及类型参数语法,函数和类可以直接写 def first[T](items: list[T]) -> T:(PEP 695)。同时 typing.TypeAlias 被标记为废弃,官方建议改用 type 语句,并计划在 3.18 移除。
  • 3.14:注解的求值时机发生根本变化,见下一节。
python 复制代码
# 适用于 Python 3.12+

# type 语句声明类型别名(3.12 起)

type Vector = list[float]



# 类型参数语法(3.12 起,PEP 695)

def first[T](items: list[T]) -> T:

    return items[0]



v: Vector = [1.0, 2.0]

print(first([10, 20, 30]))     # 10

print(first(v))                # 1.0

在 3.12 之前要表达同样的意思,得这样写:

python 复制代码
# 适用于 Python 3.8+

from typing import TypeVar, List



T = TypeVar("T")



def first(items: List[T]) -> T:

    return items[0]



Vector = List[float]

print(first([10, 20, 30]))

四、PEP 649 / 749:3.14 的延迟求值

这是本节标题里「最新」二字真正的落点。

在 3.14 之前,函数、类、模块上的注解会在定义那一刻 立即求值。这带来一个麻烦:如果注解里引用了还没定义的名字(比如类里引用自身),就会在定义时抛 NameError。过去的对策是把注解写成字符串,或者加一句 from __future__ import annotations(PEP 563),让所有注解都变成字符串存着、永不求值。

Python 3.14 起,注解不再在定义时立即求值 ,而是延迟到真正需要读取时才求值(PEP 649,配套的落地实现是 PEP 749)。好处有两方面:定义大量带注解的函数和类时,少了立即求值的开销;同时不需要再给前向引用套引号,也不必为了这个去开 from __future__ import annotations。

需要读取注解时,标准库提供了 annotationlib 模块,可以指定按哪种格式取:

格式 含义

|----------------|----------------------------|
| Format.VALUE | 求值成真实的运行时对象,最接近 3.14 之前的行为 |

|---------------------|--------------------------|
| Format.FORWARDREF | 尽量求值,求不出来的部分用前向引用占位,不抛异常 |

|-----------------|------------------------------------------------------|
| Format.STRING | 返回字符串形式,等价于 from __future__ import annotations 的效果 |

python 复制代码
# 适用于 Python 3.14+

from annotationlib import get_annotations, Format



def f(x: Undefined, y: "int") -> None:   # Undefined 从未定义,也不报错

    pass



print(get_annotations(f, format=Format.STRING))      # 全部以字符串形式给出

print(get_annotations(f, format=Format.FORWARDREF))  # 能求的求真,不能的占位

上面这段在 3.14 之前会在 def 那一行就抛 NameError。如果运行环境低于 3.14,要在定义前把 Undefined 定义出来,或改用字符串注解、加 from __future__ import annotations。

再看前向引用:3.14 之前,类里引用自身的方法返回值常常得写成字符串。

python 复制代码
# 适用于 Python 3.8+

class Node:

    def __init__(self, value: int, nxt: "Node" = None) -> None:

        self.value = value

        self.nxt = nxt



    def __repr__(self) -> str:

        return f"Node({self.value})"



print(Node(1, Node(2)))

在 3.14 及以上,"Node" 的引号可以去掉,直接写 nxt: Node = None 也不会报错。但为了兼容更老的版本,保留引号仍然是最保险的写法------引号在任何 3.x 下都合法。

常见坑点

  1. 以为注解会在运行时拦截错误类型。

❌ 依赖 def f(x: int) -> int 来保证调用方一定传整数。

✅ 注解只给静态检查器和 IDE 用;要运行时校验必须自己写判断,或使用会读注解并校验的第三方库。

  1. 在 3.9 之前的版本写 list[int]。

❌ 在 3.8 上执行 def f(x: list[int]) -> None:,运行到这一行会抛 TypeError。

✅ 3.9 以下用 from typing import List 写 List[int],或加 from __future__ import annotations 让注解不求值。

  1. 把 Optional[X] 当成「可以省略这个参数」。

❌ 写 def f(x: Optional[int]) 却希望调用方能不传 x。

✅ Optional[int] 只表示「值可能是 None」;「可以不传」必须靠默认值 x: int | None = None。

  1. 注解里引用了当前作用域没有的名字。

❌ def f(x: Undefined) -> None: 在 3.14 之前会在定义时就抛 NameError。

✅ 3.14 起延迟求值,不会立刻报错;更早的版本加 from __future__ import annotations,或用字符串注解。

  1. 用了已废弃的类型别名写法。

❌ 3.12 之后继续用 typing.TypeAlias / typing.List / typing.Dict 写新代码。

✅ 类型别名改用 type X = ... 语句;容器泛型改用内置的 list[...]、dict[...]。

  1. 把 Any 当成万能安全阀。

❌ 给所有拿不准的参数都标 Any,等于把类型检查关掉。

✅ 尽量给出具体的联合类型;Any 只在确实无法表达时局部使用。

  1. 以为 __annotations__ 取到的永远是运行时对象。

❌ 写了 from __future__ import annotations 后,还指望 f.__annotations__ 里的值是真实类型------它存的是字符串。

✅ 需要真实类型时用 typing.get_type_hints(f) 求值,或在 3.14 及以上用 annotationlib 指定格式。

  1. 类型注解写对了就以为代码一定对。

❌ 用类型检查器零报错作为「逻辑正确」的证明。

✅ 类型检查只覆盖类型层面的错误,逻辑错误、边界条件、并发问题它一概不管。

总结

特性 引入版本 说明

|------|------------------------|--------------|
| 类型注解 | 3.0(函数注解)/ 3.5(typing) | 元数据,不参与运行时校验 |

|--------------------------------------|--------------|-------------|
| from __future__ import annotations | 3.7(PEP 563) | 让注解以字符串形式保存 |

|------------------|-----|------------------|
| 内置泛型 list[int] | 3.9 | 替代 typing.List |

|-------------|-----|------|
| 联合写法 `X \ | Y` | 3.10 |

|------------------|------|----------------|
| type 语句、类型参数语法 | 3.12 | 替代 TypeAlias |

|------------|---------------------|------------------------------|
| 注解延迟求值默认开启 | 3.14(PEP 649 / 749) | 定义时不再求值,配 annotationlib 读取 |

用新代码时记住三条:容器泛型写内置形式、联合类型写 |、别再从 typing 里搬那些已被内置类型取代的别名。3.14 的延迟求值让注解「什么时候算出来」这件事变得可控,但「注解不参与运行时校验」这条铁律,从 3.0 到现在都没变过。

相关推荐
豆角焖肉1 小时前
SpringBoot+JWT 实现前后端分离登录鉴权
java·开发语言·spring boot·jwt·前后端分离·登录认证
Python图像识别1 小时前
46-【2027毕设】YOLO11田间杂草检测识别系统 - Python完整源码+PyQt5界面+训练模型+数据集
python·qt·课程设计
计算机编程指导师1 小时前
计算机毕设选题:基于Spark+Django的水质多指标关联分析与可视化系统源码 毕业设计 选题推荐 数据分析 机器学习
大数据·hadoop·python·spark·毕业设计·课程设计·水质
Wang's Blog1 小时前
Java 项目部署之 Docker工具快速入门: 自定义镜像的分层结构与写时复制
java·开发语言·docker
小白快快跑哦1 小时前
python-字符串全解(六):正则表达式-分组
python·mysql·正则表达式·分组
计算机编程指导师1 小时前
【大数据毕设】Hadoop+Spark台北市住宅价格数据可视化分析系统源码 毕业设计 选题推荐 毕设选题 数据分析 机器学习
大数据·hadoop·python·spark·毕业设计·课程设计·住宅价格
高级程序源9 小时前
django校园寻物通失物招领平台32261-计算机课程设计、毕业设计
vue.js·python·mysql·小程序·django·flask·课程设计
WeiXin_DZbishe9 小时前
基于springboot宠物领养系统-计算机毕设 附源码【课程设计】94217
vue.js·spring boot·python·django·php·课程设计
kimnoic10 小时前
Python数据库学习心得:SQLite、MySQL、PostgreSQL优缺点
开发语言·python