《Python编程入门》1.2 Python 2 到 3、发布节奏与版本选择

本节讲清 Python 历史上最痛的一次断代:2008 年 Python 3.0 为什么必须与 Python 2 决裂,print 语句、整数除法、str 与 bytes 这三处改动到底带来了什么,以及迁移的代价。随后介绍 PEP 602 确立的年度发布节奏与五年支持期,用一张表对比 3.12、3.13、3.14 的实际差异,并给出版本选择建议。读完本节,你能对「该用哪个版本」给出有依据的答案。

本节目标:弄明白 Python 2 到 3 为什么要断代、今天该选哪个 Python 版本,以及「3.12+」这个基线是怎么来的。
适用版本:Python 3.12+(实测 3.14.6)

1.2 Python 2 到 3、发布节奏与版本选择

上一节我们说「显式优于隐式」是 Python 的底层哲学。这条哲学在 2008 年引发了一场持续十二年的地震——Python 3.0 为了修正历史遗留的「隐式」设计,不惜与 Python 2 断代。这一节我们把这段历史和今天的版本现状讲清楚。

1.2.1 2008 年:为什么必须断一次

Python 3.0 于 2008 年 12 月发布。它不是一次普通的升级,而是一次故意不向后兼容的重写。原因很简单:Python 2 里积累了太多「隐式」行为,越晚修越贵。

Guido 的团队做了一个在当时看起来近乎疯狂的决定——同一个语言维护两条线:Python 2 继续修 bug、加功能,Python 3 慢慢重建生态。他们原以为迁移只要几年,结果拖了十二年。

1.2.2 三处具体的断裂

Python 3 改的东西很多,但对日常代码影响最大的是下面三处。它们共同的主题都是「消灭隐式」。

第一处:print 从语句变成函数。 Python 2 里 print 是关键字,写法是:

print "hello"        # Python 2 写法

这行代码在 Python 3 里直接是语法错误:

SyntaxError: Missing parentheses in call to 'print'. Did you mean print(...)?

改成函数后,print("hello") 才统一了——它变成了普通函数,可以被传参、被替换、被 file=... 定向输出。代价是所有 Python 2 代码都得改。

第二处:整数除法。 Python 2 里两个整数相除,结果还是整数(向下取整),这是 C 语言的传统,但极易出错:

print(7 / 2)    # Python 3 的结果
print(7 // 2)   # 想要整除,必须显式写 //

真实输出:

3.5
3

在 Python 2 里 7 / 2 会得到 3。这个改动让 type(7 / 2) 从 int 变成了 float——显式的 // 表示整除,/ 一律表示真除法,这正是「显式优于隐式」的落地。

第三处:str 与 bytes 分离,文本默认 Unicode。 这是最深远的一处。Python 3 里 str 是「文本」,bytes 是「字节」,两者不能隐式混用:

s = "café"
b = s.encode("utf-8")
print("str len:", len(s), "bytes len:", len(b))
print("bytes:", b)

真实输出:

str len: 4 bytes len: 5
bytes: b'caf\xc3\xa9'

café 作为文本是 4 个字符,编码成 UTF-8 后是 5 个字节(é 占两个字节)。Python 2 的 str 其实是「字节串」,导致中文、emoji 处理起来处处是坑。Python 3 把这件事显式化了:你要处理文本就用 str,要处理字节就用 bytes,编码解码必须显式调用 .encode() / .decode()。

1.2.3 2008 到 2020:漫长的迁移

为了让迁移可行,Python 生态提供了几种机制:

工具 / 机制作用
from __future__ import ...在 Python 2 里提前启用部分 Python 3 行为
2to3 工具自动把 Python 2 代码转换成 Python 3 代码
six 库写一份代码同时兼容 2 和 3
python-future更完整的兼容层

例如在 Python 2 里,你可以这样提前体验真除法:

from __future__ import division

Python 2.7 于 2010 年发布,是 2.x 的最后一个版本。官方原定 2015 年停止支持,一再延后,最终在 2020 年 1 月 1 日正式 EOL(End of Life)。此后连安全补丁都不再有。如果你今天还在维护 Python 2 代码,唯一正确的方向是迁移——这段历史的完整叙事可以看专题文章 Python 发展史:众人的语言(第一卷) 。

1.2.4 PEP 602:年度发布节奏

早期 Python 的发布没有固定节奏,3.0 到 3.1 隔了大半年,3.1 到 3.2 又隔了一年多。开发者没法规划「什么时候能用到新特性」。

PEP 602 确立了新的节奏,从 Python 3.9 开始生效:

  • 每年 10 月发布一个 feature release(3.10、3.11、3.12……依此类推)。
  • 每个版本提供约 5 年支持:先是 bugfix 阶段,之后进入只修安全问题的阶段。

这条规则对你有两个直接影响:一是你可以预期「每年十月有个新版本」,二是你可以放心用一个版本好几年,不必频繁升级。

1.2.5 3.12、3.13、3.14 的实际差异

光看版本号没意义,关键是每个版本给了什么。下表是本书主控在 Python 3.14.6 上逐条实测确认的结果:

版本已实测可用的能力
3.11ExceptionGroup / except*、asyncio.TaskGroup、asyncio.timeout、tomllib
3.12PEP 695 泛型语法(type X = ...、class C[T]:、def f[T]())、itertools.batched、pathlib.Path.walk、typing.override、sys.monitoring
3.13自由线程构建(实验性,PEP 703)、实验性 JIT、改进的交互式 REPL、typing.TypeIs、copy.replace、warnings.deprecated、os.process_cpu_count、dbm.sqlite3、PEP 594 移除「死电池」
3.14PEP 649/749 注解延迟求值(annotationlib)、PEP 734 多解释器(concurrent.interpreters)、PEP 750 模板字符串 t"..."、PEP 758 except 免括号、PEP 765 finally 中的控制流降级为警告、PEP 784 标准库 compression.zstd

其中 3.12 的 PEP 695 泛型语法是本书第 9 章的重点——它让类型参数从 TypeVar 的繁琐写法收敛成 class Box[T]: 这种「明显的方式」。而 3.14 的 t"..." 模板字符串、except 免括号,都可以在本机直接验证:

# 3.14:except 可以省略括号(PEP 758)
try:
    raise ValueError("demo")
except ValueError, TypeError:
    print("PEP 758 except-no-parens: OK")

# 3.14:模板字符串(PEP 750)
name = "Ada"
t = t"hello {name}"
print("t-string type:", type(t).__name__)

真实输出:

PEP 758 except-no-parens: OK
t-string type: Template

注意 t"..." 得到的不是 str,而是一个 Template 对象——这正是「显式优于隐式」的延续:模板把「插值」和「最终字符串」分成了两步,方便在拼接前做转义或校验。

1.2.6 3.15:还在预览,别当既成事实

写这一节时,Python 3.15 尚处于 3.15.0rc3(release candidate,候选发布版)阶段,还没有正式发布。所以本书提到 3.15 时,一律说「计划 / 预览」,不会把它当成已经可用的稳定版本。

如果你在别的文章里看到「3.15 已经支持某某特性」,先去看那篇文章的日期。RC 阶段的东西随时可能变,只有正式版(3.15.0)发布后,相关行为才算定下来。

1.2.7 如何确认你手上的版本

版本建议再好,也得先知道你手上跑的是哪一个。最直接的方式:

python3 --version
Python 3.14.6

在代码里则可以查 sys.version_info,它是一个具名元组,可以直接和版本号比较:

import sys
print(sys.version_info)
if sys.version_info >= (3, 12):
    print("可以使用 PEP 695 泛型语法")

真实输出(3.14.6):

sys.version_info(major=3, minor=14, micro=6, releaselevel='final', serial=0)
可以使用 PEP 695 泛型语法

注意最后那个 releaselevel='final'——正式版是 final,候选版是 candidate。做特性检测时,优先用 sys.version_info 而不是解析版本字符串,前者是结构化的,不会因为字符串格式变化而失效。

1.2.8 近年版本发布时间线

把 PEP 602 生效后的版本排成表,「每年十月一个版本」的节奏就一目了然:

版本首次发布状态(本文写作时)
3.102021-10已进入安全修复期
3.112022-10已进入安全修复期
3.122023-10仍在支持期内,本书基线
3.132024-10仍在支持期内
3.142025-10当前稳定线
3.15未发布3.15.0rc3(预览)

表中每个正式版本的首次发布都在十月,这正是 PEP 602 规定的节奏。具体日期以 Python 官方下载页 为准——记住这个页面,它是判断「某版本是否已发布」的唯一权威来源。

1.2.9 版本选择建议

结合上面的节奏与特性,本书给出如下建议:

场景建议版本理由
新项目3.12 及以上3.12 是本书基线,能用上 PEP 695 等现代语法
生产环境当前稳定线 3.14稳定、有完整支持期、生态适配充分
老项目至少升到仍在支持期内的版本Python 2 与已 EOL 的 3.x 没有安全补丁
尝鲜3.15 的 RC 版本只能用于试验,不要放进生产

在项目里,这个选择会写进 pyproject.toml:

[project]
name = "myapp"
requires-python = ">=3.12"

requires-python = ">=3.12" 就是本书的基线声明。它不承诺「只能在 3.12 上跑」,而是告诉安装工具:「低于 3.12 的环境请拒绝安装。」这样你就能放心使用 3.12 引入的语法,而不用为 3.9、3.10 的兼容性写降级代码。

1.2.10 一张图记住这段历史

把关键节点串起来:

  • 1991 0.9.0 发布,Python 起步。
  • 2000 2.0 发布,奠定 2.x 时代。
  • 2008 3.0 发布,与 2.x 断代。
  • 2010 2.7 发布,2.x 收尾。
  • 2019 PEP 602 确立年度节奏。
  • 2020-01-01 Python 2 正式 EOL。
  • 今天 稳定线是 3.14,3.15 在 RC 阶段。

理解了这段历史,你就不会再问「为什么教程里的代码在我这儿跑不通」——十有八九,那是 Python 2 时代的写法。

小结

  • Python 3.0 于 2008 年发布,是一次故意不向后兼容的重写,核心是消灭历史遗留的隐式行为。
  • 三处关键断裂是 print 变函数、整数除法改真除法、str 与 bytes 分离并默认 Unicode。
  • 迁移拖了十二年,Python 2 于 2020 年 1 月 1 日正式 EOL。
  • PEP 602 确立每年 10 月发布、每个版本约 5 年支持的节奏。
  • 新项目用 3.12+,生产用当前稳定线 3.14,3.15 尚在 3.15.0rc3,不得当成既成事实。

下一节我们从历史转向内部:Python 说自己是「解释型语言」,但它到底是怎么把一行源码变成执行结果的?搞懂这一点,你才能理解为什么它既不算纯解释、也不算纯编译。

阅读导航:上一节:1.1 Python 的诞生与设计哲学 · 下一节:1.3 解释器实现与执行模型 。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「python」更多文章

  1. 《Python高级编程》目录
  2. 《Python高级编程》11.3 PEP 流程与版本迁移策略
  3. 《Python高级编程》11.2 嵌入式与自由线程运行时