工作台

书架 · 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" - selfthis,但必须显式传:调用 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 开发里你天天会定义 MessageToolCall 这类数据类,优先用 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 - 记住公式:@decofunc = 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 Stringlist[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-工程化与异步