跳转至

📖 Python 3.12/3.13新特性与Polars

学习时间:4-5小时 | 难度:⭐⭐⭐ | 前置:01-Python核心基础

💡 Python持续进化:更好的类型系统、更快的执行速度、更强的数据处理库。

Python 3.12/3.13 与 Polars主题概览


📖 1. Python 3.12 新特性

1.1 改进的类型参数语法(PEP 695)

Python 3.12引入了全新的泛型语法,告别冗长的 TypeVar

Python
# === Python 3.11 旧写法 ===
from typing import TypeVar, Generic

T = TypeVar('T')
U = TypeVar('U')

class Stack(Generic[T]):
    def __init__(self) -> None:
        self._items: list[T] = []

    def push(self, item: T) -> None:
        self._items.append(item)

    def pop(self) -> T:
        return self._items.pop()

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

# === Python 3.12 新写法 ===
class Stack[T]:            # 直接在类名后声明类型参数
    def __init__(self) -> None:
        self._items: list[T] = []

    def push(self, item: T) -> None:
        self._items.append(item)

    def pop(self) -> T:
        return self._items.pop()

def first[T](items: list[T]) -> T:  # 函数泛型也简化了
    return items[0]

# 类型别名新语法
type Point = tuple[float, float]            # 简单别名
type Matrix[T] = list[list[T]]             # 泛型别名
type RecursiveList[T] = T | list["RecursiveList[T]"]  # 递归类型

1.2 改进的f-string(PEP 701)

Python
# Python 3.12: f-string支持任意表达式,包括嵌套引号
names = ["Alice", "Bob", "Charlie"]

# 3.12之前:f-string内不能用相同引号
# 3.12:可以了!
msg = f"Names: {", ".join(names)}"
print(msg)  # Names: Alice, Bob, Charlie

# 多行表达式
result = f"""
{
    sum(
        x**2
        for x in range(10)
    )
}
"""

# 嵌套f-string
data = {"name": "Alice", "score": 95}
report = f"{'='*20}\n{f'{data["name"]}: {data["score"]}'}\n{'='*20}"
print(report)

1.3 Per-Interpreter GIL(PEP 684)

Python
# Python 3.12: 支持每个子解释器有独立的GIL
# 这是未来移除GIL的第一步

# 目前通过C API使用(Python 3.13将暴露Python接口)
# 概念示意:
"""
主解释器 (GIL-1)
  └── 子解释器A (GIL-2)  ← 独立GIL,真正并行
  └── 子解释器B (GIL-3)  ← 独立GIL,真正并行

vs 传统多线程:
主解释器 (GIL)
  └── 线程A ← 共享GIL,无法真正并行
  └── 线程B ← 共享GIL,无法真正并行
"""

1.4 性能改进

Python
# Python 3.12 性能亮点:
# 1. 推导式内联(Comprehension Inlining)- 不再创建额外函数帧
# 2. 更小的对象内存占用
# 3. asyncio性能提升

import sys
print(f"Python版本: {sys.version}")

# 性能测试示例
import time

def benchmark_comprehension(n=1_000_000):
    """推导式性能(3.12比3.11快约2倍)"""
    start = time.perf_counter()
    result = [x * x for x in range(n)]
    elapsed = time.perf_counter() - start
    print(f"推导式 {n}项: {elapsed*1000:.1f}ms")

benchmark_comprehension()

📖 2. Python 3.13 新特性

2.1 Free-threaded Python(PEP 703)

Python
# Python 3.13: 实验性移除GIL!
# 安装: python3.13t(带t后缀表示free-threaded版本)

# 传统Python: GIL限制CPU密集型多线程
# Free-threaded: 真正的多线程并行

import threading
import time

def cpu_bound(n):
    """CPU密集型任务"""
    total = 0
    for i in range(n):
        total += i * i
    return total

# 多线程测试
def test_multithreading():
    n = 10_000_000

    # 单线程
    start = time.perf_counter()
    cpu_bound(n)
    cpu_bound(n)
    single = time.perf_counter() - start

    # 双线程
    start = time.perf_counter()
    t1 = threading.Thread(target=cpu_bound, args=(n,))  # threading.Thread创建新线程执行并发任务
    t2 = threading.Thread(target=cpu_bound, args=(n,))
    t1.start(); t2.start()
    t1.join(); t2.join()
    multi = time.perf_counter() - start

    print(f"单线程: {single:.3f}s")
    print(f"双线程: {multi:.3f}s")
    print(f"加速比: {single/multi:.2f}x")
    # 传统Python: ~1.0x (GIL限制)
    # Free-threaded: ~1.8-2.0x (真正并行!)

test_multithreading()

2.2 改进的REPL

Python
# Python 3.13: 全新的交互式REPL
# 特性:
# - 多行编辑(上下键浏览历史时保持完整代码块)
# - 语法高亮(关键字/字符串/数字不同颜色)
# - 更好的错误提示
# - Tab自动补全增强
# - 支持paste mode(粘贴多行代码自动检测)

# 错误信息改进示例:
"""
>>> x = [1, 2, 3
... ]
>>> x[5]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    x[5]
    ~^^^
IndexError: list index out of range

# 3.13的错误提示更精确,用 ^^^ 箭头指向问题位置
"""

2.3 JIT编译器(实验性)

Python
# Python 3.13引入实验性JIT编译器(copy-and-patch JIT)
# 需要编译时开启: --enable-experimental-jit

# JIT性能现状(实验性基础设施):
# - 3.13 JIT 当前性能尚不如解释器(copy-and-patch 初始实现)
# - 3.15+ 预期仅个位数的百分比提升
# - 与PyPy不同,CPython JIT更轻量,保持C扩展兼容性
# - 重点是基础设施搭建,为未来优化铺路

# 检查JIT是否启用
import sys
print(f"Python {sys.version}")
# 3.13+: sys.flags中会包含jit相关flag

2.4 TaskGroup改进

Python
import asyncio

async def fetch_data(url: str) -> dict:  # async def定义协程函数,await暂停等待异步结果
    """模拟异步请求"""
    await asyncio.sleep(0.1)
    return {"url": url, "status": 200}

async def main():
    # Python 3.11+ TaskGroup(3.13进一步优化)
    async with asyncio.TaskGroup() as tg:
        task1 = tg.create_task(fetch_data("https://api1.com"))
        task2 = tg.create_task(fetch_data("https://api2.com"))
        task3 = tg.create_task(fetch_data("https://api3.com"))

    # 所有任务完成后才到这里
    results = [task1.result(), task2.result(), task3.result()]
    print(f"获取 {len(results)} 个结果")

    # 3.13改进: 更好的异常传播和取消处理
    # 任何任务失败会自动取消其他任务(结构化并发)

asyncio.run(main())  # asyncio.run()启动异步事件循环并执行协程

📖 3. Polars:下一代DataFrame库

3.1 为什么用Polars替代Pandas?

特性 Pandas Polars
底层 NumPy (Python/C) Rust + Apache Arrow
多线程 不支持 自动并行
惰性求值 不支持 支持(查询优化)
内存效率 一般 出色(零拷贝)
大数据 OOM爆内存 流式处理/惰性
速度 基准线 5-100x更快

3.2 Polars基础操作

Python
import polars as pl

# 创建DataFrame
df = pl.DataFrame({
    "name": ["Alice", "Bob", "Charlie", "Diana", "Eve"],
    "age": [25, 30, 35, 28, 32],
    "department": ["AI", "Backend", "AI", "Frontend", "AI"],
    "salary": [50000, 60000, 75000, 55000, 70000]
})

print(df)
print(f"Schema: {df.schema}")

# 基础筛选和选择
result = df.filter(
    pl.col("department") == "AI"
).select(
    "name", "salary"
).sort("salary", descending=True)

print("\nAI部门(按薪资降序):")
print(result)

3.3 表达式系统(Polars的核心优势)

Python
import polars as pl
import numpy as np

# 生成大数据集
np.random.seed(42)
n = 1_000_000
df = pl.DataFrame({
    "user_id": np.random.randint(1, 10001, n),
    "product_id": np.random.randint(1, 1001, n),
    "amount": np.random.uniform(10, 1000, n).round(2),
    "category": np.random.choice(["电子", "服装", "食品", "家居"], n),
    "date": pl.date_range(pl.date(2024, 1, 1), pl.date(2024, 12, 31),
                          eager=True).sample(n, with_replacement=True)
})

# 链式表达式(声明式,可自动优化)
result = (
    df
    .group_by("category")
    .agg(
        pl.col("amount").sum().alias("总销售额"),
        pl.col("amount").mean().alias("平均客单价"),
        pl.col("user_id").n_unique().alias("独立用户数"),
        pl.col("amount").quantile(0.95).alias("P95金额"),
        (pl.col("amount") > 500).sum().alias("大额订单数"),  # 布尔表达式求和:True计为1,统计满足条件的行数
    )
    .sort("总销售额", descending=True)
    .with_columns(
        (pl.col("总销售额") / pl.col("总销售额").sum() * 100)  # .sum()在表达式内计算该列总和,实现"每行值/总和*100"求占比
        .round(2)
        .alias("销售占比%")
    )
)

print("按品类统计:")
print(result)

3.4 惰性求值(LazyFrame)

Python
import polars as pl

# 惰性模式:构建执行计划,优化后再执行
lazy_result = (
    pl.scan_csv("sales_data.csv")  # 不立即读取
    .filter(pl.col("amount") > 100)
    .group_by("category")
    .agg(pl.col("amount").sum())
    .sort("amount", descending=True)
    .head(10)
)

# 查看执行计划(Polars会自动优化)
print("执行计划:")
print(lazy_result.explain())

# 实际执行
# result = lazy_result.collect()

# 优化示例:
# - 谓词下推: filter在group_by之前执行
# - 投影下推: 只读取需要的列
# - 公共子表达式消除

3.5 窗口函数

Python
import polars as pl

df = pl.DataFrame({
    "name": ["Alice", "Bob", "Charlie", "Diana", "Eve", "Frank"],
    "department": ["AI", "AI", "AI", "Backend", "Backend", "Backend"],
    "performance": [95, 88, 92, 85, 90, 78]
})

result = df.with_columns(
    # 部门内排名
    pl.col("performance")
        .rank(descending=True)
        .over("department")
        .alias("部门排名"),

    # 部门平均分
    pl.col("performance")
        .mean()
        .over("department")
        .alias("部门平均"),

    # 与部门平均的差距
    (pl.col("performance") - pl.col("performance").mean().over("department"))
        .round(1)
        .alias("偏差")
)

print(result)

3.6 Polars vs Pandas 性能对比

Python
import polars as pl
import pandas as pd
import numpy as np
import time

def benchmark_comparison(n=5_000_000):
    """Polars vs Pandas性能对比"""
    # 生成数据
    np.random.seed(42)
    data = {
        "group": np.random.choice(["A", "B", "C", "D", "E"], n),
        "value1": np.random.randn(n),
        "value2": np.random.randn(n),
    }

    # Pandas
    df_pd = pd.DataFrame(data)
    start = time.perf_counter()
    result_pd = df_pd.groupby("group").agg({"value1": "mean", "value2": "sum"})
    pandas_time = time.perf_counter() - start

    # Polars
    df_pl = pl.DataFrame(data)
    start = time.perf_counter()
    result_pl = df_pl.group_by("group").agg(
        pl.col("value1").mean(),
        pl.col("value2").sum()
    )
    polars_time = time.perf_counter() - start

    print(f"数据量: {n:,}行")
    print(f"Pandas:  {pandas_time*1000:.1f}ms")
    print(f"Polars:  {polars_time*1000:.1f}ms")
    print(f"加速比:  {pandas_time/polars_time:.1f}x")

benchmark_comparison()

3.7 AI/ML场景中的Polars

Python
import polars as pl

# 特征工程场景(Polars擅长的领域)
def feature_engineering(df: pl.DataFrame) -> pl.DataFrame:
    """用Polars做ML特征工程"""
    return df.with_columns(
        # 滚动统计
        pl.col("amount").rolling_mean(window_size=7).alias("7日均值"),
        pl.col("amount").rolling_std(window_size=7).alias("7日标准差"),

        # 滞后特征
        pl.col("amount").shift(1).alias("前1天"),
        pl.col("amount").shift(7).alias("前7天"),

        # 分组统计编码
        pl.col("amount").mean().over("category").alias("品类均值编码"),

        # 时间特征
        pl.col("date").dt.weekday().alias("星期几"),
        pl.col("date").dt.month().alias("月份"),

        # 组合特征
        (pl.col("amount") / pl.col("amount").mean().over("category"))
        .alias("相对品类均值"),
    ).drop_nulls()  # 去除滚动计算产生的null

# Polars与sklearn配合
# df_features = feature_engineering(df)
# X = df_features.select(feature_cols).to_numpy()  # 零拷贝转numpy
# y = df_features["target"].to_numpy()

📖 4. 其他重要新特性

4.1 match-case高级用法(3.10+,3.12更稳定)

Python
from dataclasses import dataclass

@dataclass  # @dataclass自动生成__init__、__repr__等常用方法
class Point:
    x: float
    y: float

@dataclass
class Circle:
    center: Point
    radius: float

@dataclass
class Rectangle:
    top_left: Point
    bottom_right: Point

def describe_shape(shape):
    """结构模式匹配 - 强大的解构能力"""
    match shape:  # match-case是Python 3.10+的结构化模式匹配语法
        # 解构dataclass
        case Circle(center=Point(x=0, y=0), radius=r):
            return f"原点处的圆,半径={r}"

        case Circle(center=Point(x=cx, y=cy), radius=r) if r > 10:
            return f"大圆在({cx},{cy}),半径={r}"

        case Rectangle(top_left=Point(x=x1, y=y1),
                       bottom_right=Point(x=x2, y=y2)):
            w, h = abs(x2 - x1), abs(y2 - y1)
            return f"矩形 {w}x{h}"

        case _:
            return "未知形状"

# 测试
shapes = [
    Circle(Point(0, 0), 5),
    Circle(Point(3, 4), 15),
    Rectangle(Point(0, 0), Point(10, 20))
]

for s in shapes:
    print(f"{s}{describe_shape(s)}")

4.2 更好的错误信息(3.11-3.13持续改进)

Python
# Python 3.11+的精确错误定位
"""
示例1:
  x = {"key": "value"}
  y = x["missing_key"]
       ~~^^^^^^^^^^^^^^^
  KeyError: 'missing_key'

示例2:
  result = a + b * c / d
                    ~~^~~
  ZeroDivisionError: division by zero

示例3:
  obj.attr1.attr2.attr3
  ~~~~~~~~~~^^^^^
  AttributeError: 'NoneType' object has no attribute 'attr2'

# 3.13进一步改进:
# - 更好的建议 "did you mean ...?"
# - 导入错误的自动建议
"""

🎯 面试高频题

Q1: Python 3.12/3.13最重要的改进是什么?

A: 3.12最大改进是新的类型参数语法(PEP 695)和Per-Interpreter GIL。3.13最大改进是实验性的free-threaded模式(移除GIL)和JIT编译器。这两个版本共同指向Python的未来方向:更好的并行性和更快的执行速度。

Q2: Polars和Pandas的核心区别?

A: Polars用Rust编写+Apache Arrow内存格式,原生支持多线程和惰性求值。Pandas用Python/C+NumPy。在大数据(>100万行)场景下Polars通常快5-30倍,内存占用更少。Polars的表达式系统可以自动优化执行计划(谓词下推/投影下推)。

Q3: 什么是惰性求值?Polars如何利用它?

A: 惰性求值是"声明计算但不立即执行"。pl.scan_csv()不读文件,只记录计划。链式操作构建执行图。调用.collect()时,Polars优化器自动重排操作(如filter提前、只读需要的列),然后并行执行。这类似SQL查询优化器。

Q4: Free-threaded Python意味着GIL彻底被移除了?

A: 正在进行中。3.13是实验性的(需要python3.13t),3.14已将free-threaded模式升级为非实验性(PEP 779,Phase 2)。完全移除GIL可能在3.15-3.16完成。过渡期间两种模式共存。主要挑战是保证C扩展的线程安全,numpy等库需要适配。

Q5: 在ML项目中什么时候该用Polars?

A: 数据量>50万行的特征工程、ETL管道、数据探索。Polars的group_by/窗口函数/惰性求值特别适合大规模特征工程。但sklearn/PyTorch等ML库期望numpy/pandas输入,需要.to_numpy()转换。小数据集(<10万行)Pandas足够,没必要换。

Q6: type别名和TypeAlias的区别?

A: Python 3.12引入type X = ...语法,是语言层面的类型别名。之前的TypeAlias = ...是typing模块的标注。新语法支持前向引用和递归类型(type Tree[T] = T | list[Tree[T]]),旧方式做不到。


✅ 学习检查清单

  • 能用Python 3.12的新语法写泛型类和函数
  • 了解free-threaded Python的原理和限制
  • 能用Polars完成数据加载、筛选、聚合、窗口函数
  • 理解LazyFrame和查询优化的原理
  • 能将Polars用于ML特征工程场景
  • 知道Polars vs Pandas的选择标准
  • 掌握match-case的高级模式匹配

📌 下一步:在 03-数据科学核心库 中对比Polars与Pandas在实际数据集上的性能差异。