一、自由线程:GIL 终于变成可选项

Python 3.14 最重磅的变化,是 free-threaded(无 GIL)构建正式进入支持阶段(PEP 703)。过去 CPython 靠全局解释器锁保证对象安全,代价是多核 CPU 上多线程只能“轮流跑”。现在官方提供了 python3.14t 这样的自由线程构建,CPU 密集的多线程代码终于能真正并行。

# 安装自由线程版本(pyenv 示例)
pyenv install 3.14t
pyenv global 3.14t

python -c "import sys; print(sys._is_gil_enabled())"  # False

验证并行收益:

import threading, time, sys

def burn(n: int) -> int:
    s = 0
    for i in range(n):
        s += i * i
    return s

def run(threads: int, total: int = 20_000_000):
    per = total // threads
    ts = [threading.Thread(target=burn, args=(per,)) for _ in range(threads)]
    t0 = time.perf_counter()
    [t.start() for t in ts]
    [t.join() for t in ts]
    return time.perf_counter() - t0

print("GIL enabled:", sys._is_gil_enabled())
for n in (1, 2, 4):
    print(f"threads={n}: {run(n):.2f}s")

自由线程下 4 线程的耗时大约会降到单线程的 1/3 左右,而有 GIL 的版本几乎不会变化。

注意事项:

  • 依赖 C 扩展的库需要重新编译为自由线程轮子(cp314t ABI);numpy、pandas 等主流库已逐步跟进。
  • 单线程性能会有约 5%~15% 的回退,IO 密集型服务不必急着切。
  • 共享可变状态仍需自己加锁,自由线程不等于“不用管线程安全”。

二、t-string:为模板而生的新字面量

PEP 750 引入的 t-string 与 f-string 语法一致,但求值的不是字符串,而是一个 Template 对象,可以拿到插值前后的所有片段,天然适合做 SQL 转义、HTML 过滤、日志结构化。

from string.templatelib import Template, Interpolation

def safe_sql(template: Template) -> tuple[str, tuple]:
    sql_parts: list[str] = []
    params: list[object] = []
    for item in template:
        if isinstance(item, Interpolation):
            sql_parts.append("?")
            params.append(item.value)
        else:
            sql_parts.append(item)
    return "".join(sql_parts), tuple(params)

name = "O'Brien"
tpl = t"SELECT * FROM users WHERE name = {name} AND age > {18}"
print(safe_sql(tpl))
# ('SELECT * FROM users WHERE name = ? AND age > ?', ("O'Brien", 18))

这是 f-string 长期缺失的能力:以前想在插值处做上下文相关处理,只能靠正则表达式或 AST 解析,现在语言层面直接给到。

三、注解延迟求值(PEP 649)

以前为了让注解不被立即求值,需要写 from __future__ import annotations,这会带来 typing.get_type_hints() 失效、字符串化丢失等副作用。3.14 起注解默认以“延迟对象”形式存储:

class Node:
    # 无需引号、无需 future import,也能前向引用自身
    def parent(self) -> "Node | None":
        return getattr(self, "_parent", None)

    def children(self) -> list[Node]:
        return getattr(self, "_children", [])

import typing
print(typing.get_type_hints(Node.parent))
# {'return': Node | None}

对 pydantic、FastAPI 这类重度依赖类型注解的框架来说,这既省掉了运行时开销,也让 IDE 推断更准确。

四、其他值得关注的改进

  • 更好的错误信息:解释器会给出更贴近上下文的建议,例如拼写相近的属性名。
  • 多解释器(subinterpreters):标准库暴露 concurrent.interpreters,为每个子解释器提供独立 GIL,是介于线程与进程之间的第三选项。
  • 调试器接口外部化:PEP 768 让 pdb、IDE 可以更安全地附加到运行中的进程。

小结

Python 3.14 的关键词是“把历史包袱交还给用户选择”:GIL 变成开关而非铁律、字符串插值可编程、注解不再拖慢启动。建议先在 CI 里跑一遍兼容性套件,再挑选 CPU 密集的服务试点自由线程构建。