工作台

书架 · Python 学习系列 · 02 · Python 进阶下一章:03 工程化与异步 →

02 · Python 进阶

本章目标:掌握面向对象、装饰器、生成器、上下文管理器、类型提示、常用标准库,以及新手必踩的坑。学完你能读懂主流框架(FastAPI / openai SDK / LangChain)的源码风格。

前置:学完 01-Python基础。


目录

  1. 面向对象
  2. 鸭子类型与 Protocol
  3. 闭包与装饰器
  4. 迭代器与生成器
  5. 上下文管理器 with
  6. 类型提示 typing
  7. 常用标准库速查
  8. 新手必踩的坑
  9. 自测清单
  10. 小练习
  11. 参考与出处

1. 面向对象

1.1 类的定义

class User:
    # 类属性(≈ static 字段,所有实例共享)
    species = "human"

    # 构造方法。self 就是 Java 里的 this,但必须显式写在第一个参数
    def __init__(self, name: str, age: int):
        # 实例属性(≈ 成员字段)
        self.name = name
        self.age = age

    # 实例方法:第一个参数永远是 self
    def greet(self) -> str:
        return f"我是 {self.name}"

    # 魔术方法:print(obj) 时自动调用,≈ Java 的 toString()
    def __repr__(self) -> str:
        return f"User(name={self.name!r}, age={self.age})"

    # == 判断时调用,≈ Java 的 equals()
    def __eq__(self, other) -> bool:
        if not isinstance(other, User):
            return NotImplemented
        return self.name == other.name


u = User("macao", 18)
print(u)            # User(name='macao', age=18)
u2 = User("macao", 20)
print(u == u2)      # True(只比 name)

☕ Java vs Python OOP 对照: - 没有 public/private/protected:Python 靠约定——_name 单下划线表示"内部用",__name 双下划线触发名称改写(name mangling,伪私有)。全靠自觉,这在 Python 文化里叫 "we're all consenting adults" - self ≈ this,但必须显式传:调用 u.greet() 实际执行 User.greet(u) - __init__ ≈ 构造器;__repr__/__str__ ≈ toString();__eq__ ≈ equals()(注意:定义了 __eq__ 后 hash 行为会变,可变对象默认不实现 __hash__) - 没有真正的私有方法和 final,也没有接口 interface(用 Protocol,见下节)

1.2 继承与多态

class Animal:
    def __init__(self, name: str):
        self.name = name

    def speak(self) -> str:
        raise NotImplementedError   # ≈ 抽象方法的朴素写法


class Dog(Animal):                  # 括号里写父类,没有 extends 关键字
    def speak(self) -> str:
        return f"{self.name}: 汪汪"


class Cat(Animal):
    def __init__(self, name: str, indoor: bool):
        super().__init__(name)      # super() 调父类构造,同 Java
        self.indoor = indoor

    def speak(self) -> str:
        return f"{self.name}: 喵"


# 多态不需要共同接口/继承声明——能 speak 就行(见鸭子类型)
for a in [Dog("旺财"), Cat("咪咪", False)]:   # Cat 的 indoor 无默认值,必须显式传
    print(a.speak())

Python 支持多继承(class A(B, C),方法查找按 MRO 顺序)。AI 框架源码里常见 class ChatAgent(BaseAgent[dict]) 这种写法——继承 + 泛型参数。初学阶段用单继承就够,知道多继承存在即可。

1.3 抽象基类(正统写法)

from abc import ABC, abstractmethod

class Storage(ABC):                 # ≈ abstract class
    @abstractmethod
    def save(self, key: str, value: str) -> None:
        ...

class MemoryStorage(Storage):
    def save(self, key: str, value: str) -> None:
        print(f"存到内存: {key}={value}")

# Storage() 直接实例化会报 TypeError

1.4 property:把方法伪装成字段

class Circle:
    def __init__(self, r: float):
        self._r = r                 # 惯例:内部字段加 _

    @property                       # 读 c.area 像字段,实际执行方法
    def area(self) -> float:
        return 3.14159 * self._r ** 2

    @property
    def radius(self) -> float:
        return self._r

    @radius.setter                  # c.radius = 10 时触发校验
    def radius(self, value: float):
        if value <= 0:
            raise ValueError("半径必须为正")
        self._r = value

c = Circle(2)
print(c.area)      # 12.566...  ← 注意没有括号
c.radius = 5       # 走 setter 校验

☕ ≈ Lombok 的 getter/setter 或 Kotlin 的属性,但 Python 从第一天就建议:字段直接 public 暴露,需要校验时再无痛升级成 property——调用方代码一行不用改(这也是 Python 不写 getter/setter 的原因)。

1.5 dataclass:数据类的正确打开方式

from dataclasses import dataclass, field

@dataclass
class ChatMessage:
    role: str
    content: str
    tokens: int = 0
    meta: dict = field(default_factory=dict)   # 可变默认值的正确写法


msg = ChatMessage(role="user", content="你好")
print(msg)            # 自动生成 __repr__
print(msg == ChatMessage("user", "你好"))   # 自动生成 __eq__

# 不可变版本(≈ Java record)
@dataclass(frozen=True)
class Point:
    x: float
    y: float

☕ @dataclass ≈ Lombok @Data / Java 16+ record:自动生成 __init__、__repr__、__eq__。openai SDK、FastAPI 的请求模型全是这套思路(Pydantic 是它的加强版,见第 07 章)。AI 开发里你天天会定义 Message、ToolCall 这类数据类,优先用 dataclass 或 pydantic,别手写。


2. 鸭子类型与 Protocol

"如果它走起来像鸭子、叫起来像鸭子,那它就是鸭子。"——Python 不关心对象的类型,只关心它能做什么。

def total(items):
    # 不检查类型,只要能迭代、元素支持 + 就行
    # list、tuple、生成器、numpy 数组……都能传
    return sum(items)

☕ Java 是名义类型(nominal typing):必须 implements Comparable 才算数。Python 是结构类型(structural typing):只要长了那个方法就算。这就是为什么 Python 不需要接口也能多态。

需要"接口"的约束感时,用 typing.Protocol(静态检查工具认,运行时不强制):

from typing import Protocol

class Speakable(Protocol):
    def speak(self) -> str: ...

def make_sound(x: Speakable) -> None:   # 任何有 speak() 方法的对象都满足
    print(x.speak())

3. 闭包与装饰器

3.1 函数是一等公民

# 函数可以赋值给变量、当参数传、当返回值
def shout(text: str) -> str:
    return text.upper() + "!"

func = shout            # 注意没有括号——传的是函数本身
print(func("hello"))    # HELLO!

def apply(f, value):    # 高阶函数:接收函数
    return f(value)

apply(shout, "hey")     # 'HEY!'

☕ Java 8 之后 Function<String,String> / lambda 是"模拟"一等函数,Python 里函数天生就是对象。

3.2 闭包

def make_counter():
    count = 0
    def increment():
        nonlocal count      # 闭包引用外层函数的变量
        count += 1
        return count
    return increment

counter = make_counter()
counter()   # 1
counter()   # 2  ← 状态被闭包"记住"了

3.3 装饰器(重点,框架里到处都是)

装饰器 = 接收一个函数、返回增强版函数的函数。@xxx 只是语法糖:

import functools
import time

def timer(func):
    @functools.wraps(func)            # 保住原函数的名字和文档
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        cost = time.perf_counter() - start
        print(f"{func.__name__} 耗时 {cost:.3f}s")
        return result
    return wrapper

# @timer 等价于 slow_add = timer(slow_add)
@timer
def slow_add(a, b):
    time.sleep(0.5)
    return a + b

slow_add(1, 2)     # slow_add 耗时 0.500s

带参数的装饰器(三层嵌套):

import functools

def retry(times: int):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for i in range(times):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    print(f"第 {i + 1} 次失败: {e}")
            raise RuntimeError(f"{func.__name__} 重试 {times} 次仍失败")
        return wrapper
    return decorator

@retry(times=3)
def call_api(): ...

☕ Java vs Python: - 装饰器 ≈ Java 注解 + AOP 的合体,但没有代理、没有反射、没有 IoC 容器——就是普通的函数包裹,单步就能调试进去 - FastAPI 的 @app.get("/users")、openai SDK 无关的各类 @tool、pytest 的 @pytest.fixture,本质全是本章这个 wrapper - 记住公式:@deco ⇔ func = deco(func);@deco(arg) ⇔ func = deco(arg)(func)


4. 迭代器与生成器

4.1 协议

# 可迭代对象(Iterable):实现了 __iter__
# 迭代器(Iterator):实现了 __iter__ 和 __next__

it = iter([1, 2, 3])   # 从可迭代对象取出迭代器
next(it)               # 1
next(it)               # 2
next(it)               # 3
# next(it)             # StopIteration 异常

☕ ≈ Java 的 Iterable / Iterator,连方法名都几乎一样(__iter__≈iterator(),__next__≈next())。

4.2 生成器:惰性产出的函数

函数体里出现 yield,它就变成生成器函数——调用时不执行,遍历时才逐个产出:

def read_large_file(path):
    with open(path, encoding="utf-8") as f:
        for line in f:
            yield line.strip()      # 产出一行,暂停,等下一次索取

# 先准备示例文件(真实场景是 10GB 日志)
with open("huge.log", "w", encoding="utf-8") as f:
    f.write("INFO 启动\nINFO 处理请求\nDONE\n")

# 10GB 的文件也不会撑爆内存——同一时刻内存里只有一行
for line in read_large_file("huge.log"):
    print(line)

# 生成器表达式:推导式的惰性版(圆括号)
total = sum(x * x for x in range(10_000_000))   # 不建 1000 万元素的 list
print(total)

4.3 yield from 与无限序列

def chain(*iterables):
    for it in iterables:
        yield from it          # 逐个产出子可迭代对象的元素

list(chain([1, 2], "ab"))     # [1, 2, 'a', 'b']

from itertools import count
evens = (x for x in count() if x % 2 == 0)   # 无限偶数流,用时才算

为什么 AI 开发要懂生成器:LLM 流式输出的处理就是"拿一个生成器逐 chunk 消费";FastAPI 的流式响应(SSE)接口函数就是一个 async 生成器。见第 04、07 章。


5. 上下文管理器 with

with open("a.txt", "w", encoding="utf-8") as f:   # 先准备文件
    f.write("第一行内容")

# with 块结束(或异常)时自动调用 f.__exit__() → 自动关资源
with open("a.txt", encoding="utf-8") as f:
    data = f.read()
# 这里文件已自动关闭,即使中间抛了异常
print(data)

☕ ≈ Java 7 的 try-with-resources,但不止管资源——任何"进入/退出"语义都能用:数据库事务、锁、临时目录、HTTP 连接。

自己实现:

import time

class Timer:
    def __enter__(self):
        self.start = time.perf_counter()
        return self                          # as 后面拿到的就是它
    def __exit__(self, exc_type, exc_val, exc_tb):
        print(f"耗时 {time.perf_counter() - self.start:.3f}s")
        return False                         # False = 异常继续往外抛

with Timer():
    time.sleep(0.2)

更省事的写法:

import time
from contextlib import contextmanager

@contextmanager
def timer():
    start = time.perf_counter()
    try:
        yield                                # yield 之前 = __enter__,之后 = __exit__
    finally:
        print(f"耗时 {time.perf_counter() - start:.3f}s")

with timer():
    time.sleep(0.2)                          # 换成你的业务代码

6. 类型提示 typing

Python 3.5+ 支持"类型提示":运行时不强制(写错不会报错),但 IDE 补全、静态检查(mypy / pyright)、以及 FastAPI / Pydantic 的运行时校验全靠它。现代 Python 项目必写。

# 基础:参数和返回值
def process(items: list[str], limit: int = 10) -> dict[str, int]: ...

# 3.10+ 可以用 | 代替 Union
class User:                                  # 前文 1.1 定义过的类,这里补个最小实现
    pass

def find(name: str | None = None) -> User | None: ...

# 常用容器与结构
from typing import Any, Callable, Optional

def handler(fn: Callable[[int], str], data: list[dict[str, Any]]) -> tuple[bool, str]: ...

# 类型别名(3.12+ 直接用 type 语句)
type Messages = list[dict[str, str]]

# 泛型函数
def first[T](items: list[T]) -> T:           # 3.12+ 新语法
    return items[0]

# TypedStruct 风格:用 TypedDict 描述 dict 结构(AI 开发常用!)
from typing import TypedDict

class Message(TypedDict):
    role: str
    content: str

msgs: list[Message] = [{"role": "user", "content": "hi"}]

☕ Java vs Python 类型系统: - Java 类型是编译期强制的;Python 类型提示是可选元数据——但写了之后体验接近 Java:IDE 补全、重构、检查都有 - str | None ≈ @Nullable String;list[dict[str, Any]] ≈ List<Map<String, Object>> - FastAPI 直接拿类型提示做请求校验 + 自动生成 API 文档,这是"写 Python 要带类型"的最强理由(第 07 章) - 官方手册:typing 模块文档(含各版本语法变化)


7. 常用标准库速查

collections —— 增强容器

from collections import Counter, defaultdict, deque

# Counter:计数神器(AI 分词统计、日志分析)
Counter("apple apple banana".split())       # Counter({'apple': 2, 'banana': 1})
words = "apple cherry banana banana apple banana".split()
Counter(words).most_common(3)               # 前 3 高频

# defaultdict:带默认值的 dict,免去 KeyError 判断
groups = defaultdict(list)
for name, dept in [("a", "dev"), ("b", "ops"), ("c", "dev")]:
    groups[dept].append(name)               # 不用先判断 key 存不存在

# deque:双端队列(固定长度滑动窗口:保留最近 N 轮对话!)
history = deque(maxlen=10)
history.append("msg1")                      # 超过 10 条自动挤掉最老的

datetime —— 时间

from datetime import datetime, timedelta, timezone

now = datetime.now()
utc = datetime.now(timezone.utc)
now.strftime("%Y-%m-%d %H:%M:%S")           # 格式化(≈ DateTimeFormatter)
datetime.strptime("2026-09-05", "%Y-%m-%d") # 解析
now + timedelta(days=7)                     # 偏移
datetime.timestamp(now)                     # → 秒级时间戳

pathlib —— 现代路径操作(代替 os.path)

from pathlib import Path

p = Path("data") / "logs" / "app.log"       # / 拼路径,跨平台
p.parent.mkdir(parents=True, exist_ok=True) # 先建目录
p.write_text("日志内容", encoding="utf-8")   # 写文件
p.exists(); p.is_file()                     # True True
print(p.read_text(encoding="utf-8"))        # 一行读完
p.parent; p.stem; p.suffix                  # data/logs / app / .log
print(list(Path(".").glob("*.py")))         # 通配查找

re —— 正则

import re

re.findall(r"\d+", "订单号123,金额45元")     # ['123', '45']
re.sub(r"\s+", " ", "a  b   c")             # 'a b c'
m = re.search(r"(\d{4})-(\d{2})", "2026-09")
m.group(0), m.group(1)                      # '2026-09', '2026'

enum 与 __slots__(知道即可)

from enum import Enum

class Role(Enum):
    SYSTEM = "system"
    USER = "user"
    ASSISTANT = "assistant"

Role.USER.value     # 'user' —— AI 消息角色就适合这么定义

8. 新手必踩的坑

坑 1:可变默认参数(01 章讲过,再强调:这是 Python 面试第一题)

def bad(x, acc=[]): ...      # ❌ 所有调用共享同一个 list
def good(x, acc=None): ...   # ✅

坑 2:is vs ==

a = [1, 2]; b = [1, 2]
a == b      # True  —— 值相等
a is b      # False —— 不是同一个对象(≈ Java 的 a == b 比引用)

# 小整数缓存陷阱:CPython 缓存了 -5~256
x = 256; y = 256
x is y      # True(缓存)
x = 257; y = 257
x is y      # False!(PyPy/新版本可能不同)
# 结论:比较值永远用 ==;is 只用于 None / True / False 判断

坑 3:浅拷贝 vs 深拷贝

import copy

original = {"tags": ["a", "b"]}

shallow = original.copy()          # dict() / list() / 切片 [:] 都是浅拷贝
shallow["tags"].append("c")
print(original)                    # {'tags': ['a', 'b', 'c']} ← 内层 list 被共享了!

deep = copy.deepcopy(original)     # 深拷贝,彻底独立

调 LLM 时想"留一份原始 messages 备份"再修改?必须 deepcopy(或重新组装),浅拷贝会把你备份一起改了。

坑 4:闭包晚绑定(late binding)

funcs = [lambda: i for i in range(3)]
print([f() for f in funcs])        # [2, 2, 2]!全部捕获同一个 i

# 修复:用默认参数在定义时固化
funcs = [lambda i=i: i for i in range(3)]
print([f() for f in funcs])        # [0, 1, 2]

坑 5:GIL(全局解释器锁)——先知道结论

CPython 有一把全局锁,同一时刻只有一个线程执行 Python 字节码。所以:

📌 与时俱进注:Python 3.13 起官方提供实验性的 free-threaded(无 GIL)构建,3.14 继续推进,但 2026 年的今天主流部署仍带 GIL。依据:Python 官方文档 What's New in 3.13 / free-threading 说明。初学阶段按"有 GIL"设计程序不会错。

坑 6:str 拼接性能

循环里 s += x 拼大字符串是 O(n²);用 "".join(parts)。

坑 7:None 传播

def get_user(uid: int):
    return None                              # 模拟"查无此人"的接口

data = get_user(42)
# data["key"]                               # ⚠️ None["key"] → TypeError 高发!
# 好习惯:先判空再取值
if data and data.get("key"): ...
print("安全通过")                            # data 为 None 时上面的 if 也安然无恙

9. 自测清单

10. 小练习

练习 1(OOP):用 @dataclass 定义 ChatMessage(role, content, tokens=0),再定义 Conversation 类:内部用 deque(maxlen=20) 存消息,提供 add() / total_tokens() / __repr__() 方法。

练习 2(装饰器):写 @cache 装饰器:函数结果按参数缓存(提示:闭包持有 dict,参数做 key;进一步可用 functools.lru_cache 对照)。

练习 3(生成器):写生成器 fib() 产出无限斐波那契数列,配合 itertools.islice 打印前 20 项。

练习 4(综合):手写 retry(times, delay) 装饰器:失败等待 delay 秒再重试,全部失败抛异常,用 time.sleep + 闭包实现。


11. 参考与出处

以下均为官方一手来源(访问日期:2026-09-05):

主题 出处
类与 OOP Python 官方教程 · 类
dataclass dataclasses 模块文档
typing typing 模块文档
装饰器/闭包 functools 模块文档、官方 FAQ · 装饰器
迭代器/生成器 官方教程 · 迭代器与生成器
上下文管理器 contextlib 文档
collections / datetime / pathlib / re 标准库目录
拷贝语义 copy 模块文档
GIL 与 free-threading What's New in Python 3.13

⬅️ 返回目录 | ➡️ 下一章:03-工程化与异步