Python 学习笔记

一份完整的 Python 学习笔记。学多少记多少,重点讲"为什么这么设计"(费曼式 📌 讲解),覆盖从入门到进阶的完整知识体系。

怎么用这份笔记

  1. 学习/复习 → 顺序看正文,重点看 📌 类比和"为什么"
  2. 查函数 → 翻 附录 A 速查手册
  3. 避坑 → 看 附录 B 常见陷阱
  4. 落地 → 跟 附录 C 实战项目做一遍,把知识串起来

图表在 /Python学习笔记/diagrams/ 目录。图注名即原图文件名,看图注就能直接找到原图用 draw.io 编辑。


1 · Python 概述与环境

1.1 Python 是什么

  • Python 是由 Guido van Rossum 于 1989 年(1991 年正式发布)创建的高级编程语言
  • 设计哲学:优雅、明确、简单。Python 之禅(import this)说:"Simple is better than complex"
  • 动态类型:变量不需要声明类型,运行时自动推断
  • 解释型:代码由解释器逐行执行,不需要编译(但会先编译为 .pyc 字节码缓存)
  • 垃圾回收:自动内存管理,引用计数为主,分代回收为辅
TIP

📌 打个比方:C++ 像手动挡赛车——你控制一切(内存、类型、编译),性能极致但容易熄火。Python 像自动挡轿车——帮你管好油离配合(内存、类型),你专注路线(逻辑),虽然少了一点极致性能,但到达目的地更快更轻松。

图 1 \xb7 Python 代码执行流程

1.2 Python vs C++ 对比

特性 Python C++
类型系统 动态类型 静态类型
内存管理 自动 GC 手动 / RAII
编译方式 解释执行(字节码) 编译为机器码
语法复杂度 低(缩进定义块) 高(花括号 + 头文件)
运行速度 慢(约 10-100x) 快
适用场景 脚本/AI/Web/数据 系统/游戏/嵌入式/高性能
TIP

📌 为什么 Python 慢还能流行?因为开发效率 > 运行效率。大多数程序的时间不是花在 CPU 计算上,而是在等 I/O(网络、磁盘)。Python 的慢在 I/O 密集型场景几乎无感。而真正需要性能的地方(如 NumPy、PyTorch),底层都是 C/C++ 实现的,Python 只是"胶水"。

1.3 Python 版本

版本 状态 关键变化
3.8 EOL 海象运算符 :=、仅位置参数 /
3.9 EOL 字典合并运算符 |、list[int] 原生泛型
3.10 EOL match-case 模式匹配、更好的错误信息
3.11 安全 性能提升 10-60%、异常组 ExceptionGroup
3.12 安全 f-string 嵌套改进、type 语句
3.13 最新 实验性 GIL-free 模式、JIT 编译器

建议:新项目用 Python 3.12+。Python 2 已于 2020 年停止维护,不要再用。

1.4 环境搭建

# Ubuntu
sudo apt install python3 python3-pip python3-venv

# Windows: 从 python.org 下载安装包,勾选 "Add to PATH"
# macOS
brew install python@3.12
$ python3 --version
Python 3.12.3

$ python3 -c "print('Hello, Python!')"
Hello, Python!
TIP

📌 REPL 是 Python 的杀手锏。C++ 改一行代码要等编译,Python 在 REPL 里敲一行立刻看结果。>>> help(str.split) 立刻看文档,>>> dir(obj) 看所有方法。


2 · 变量与数据类型

2.1 变量——名字标签,不是盒子

a = 10
b = a       # b 和 a 指向同一个 10
a = 20      # a 换了新标签,b 还是 10
print(b)    # 10
TIP

📌 关键理解:Python 变量不是"装值的盒子",而是"贴在对象上的标签"。b = a 不是复制盒子,而是给同一个对象再贴一个标签。a = 20 是把 a 标签撕下来贴到新对象 20 上,b 还贴在 10 上。

2.2 一切皆对象

print(type(10))           # <class 'int'>
print(type(print))        # <class 'builtin_function_or_method'>

# 数字 10 也有方法
print((10).bit_length())  # 4(二进制 1010 有 4 位)
TIP

📌 为什么一切皆对象?这简化了语言设计——不需要区分"基本类型"和"对象类型",所有变量都遵循同一套规则。对比 Java 的 int vs Integer,Python 少了一整层心智负担。

图 2 \xb7 Python 数据类型分类

2.3 基本数据类型

类型 示例 可变? 说明
int 42, 0xff 不可变 任意精度(无溢出)
float 3.14, 1e-5 不可变 IEEE 754 双精度
bool True, False 不可变 bool 是 int 的子类
str "hello" 不可变 Unicode 字符串
list [1, 2, 3] 可变 有序序列
tuple (1, 2, 3) 不可变 有序序列
dict {"k": "v"} 可变 键值映射
set {1, 2, 3} 可变 无序不重复
NoneType None 不可变 空值
# int 任意精度——C++ 溢出,Python 不会
big = 2 ** 100
print(big)  # 1267650600228229401496703205376

# bool 是 int 子类
print(True + True)    # 2
print(True == 1)      # True
TIP

📌 为什么 bool 继承 int?历史原因。Python 早期没有 bool,用 0/1 表示真假。后来加 bool 时为了向后兼容,让 True=1、False=0。这也是为什么 sum([True, False, True]) 返回 2。

2.4 可变与不可变——最重要的基础概念

# 不可变类型:修改 = 创建新对象
s = "hello"
print(id(s))        # 某地址
s += " world"
print(id(s))        # 不同地址!s 指向了新对象

# 可变类型:原地修改,地址不变
lst = [1, 2, 3]
print(id(lst))
lst.append(4)
print(id(lst))      # 同一个地址!
TIP

📌 打个比方:不可变类型像刻在石头上的字——想改只能换新石头。可变类型像白板——随时擦了重写,白板还是那块。

为什么重要?它解释了函数传参时可变默认值的陷阱,也解释了为什么字典的键必须是不可变类型。

2.5 类型转换

int("42")        # 42
int(3.99)        # 3(截断,不四舍五入)
float("3.14")    # 3.14
str(42)          # "42"
bool(0)          # False
bool("")         # False
bool([])         # False
bool("false")    # True!(非空字符串都是 True)
WARNING

🚧 bool("false") 返回 True!任何非空字符串都是 True,不管内容是什么。

2.6 is vs ==

a = [1, 2, 3]
b = [1, 2, 3]
c = a

print(a == b)    # True(值相等)
print(a is b)    # False(不是同一个对象)
print(a is c)    # True(同一个对象)

# 小整数缓存:-5 ~ 256
x = 256; y = 256
print(x is y)    # True(缓存)
x = 257; y = 257
print(x is y)    # False(不缓存)
TIP

📌 is 比较身份(id),== 比较值。规则:判断 None 用 is,其他情况用 ==。


3 · 运算符

3.1 算术运算符

7 / 2       # 3.5     真除法(总是返回 float)
7 // 2      # 3       整除(向下取整)
7 % 2       # 1       取余
7 ** 2      # 49      幂运算
-7 // 2     # -4      注意:向下取整,不是截断!
TIP

📌 // 是向下取整,不是截断。-7 // 2 = -4(不是 -3!)。C++ 整数除法是截断(向零取整)。需要 C++ 风格用 int(a / b)。

3.2 链式比较

x = 5
print(1 < x < 10)     # True,等价于 1 < x and x < 10
TIP

📌 链式比较不仅简洁,还更高效——b 只计算一次。C++ 里只能写 1 < x && x < 10。

3.3 逻辑运算符

# and / or / not(不是 && / || / !)
# 短路求值 + 返回操作数本身(不是 bool)
print(0 or "default")      # "default"
print(3 and 5)             # 5
print(0 and 5)             # 0
TIP

📌 and/or 返回的不是 True/False,而是操作数本身。实用模式:name = input_name or "匿名用户"。

3.4 海象运算符 :=(Python 3.8+)

if (n := len(data)) > 10:
    print(f"数据太长:{n} 行")

while (line := input("> ")) != "quit":
    print(f"你说:{line}")

4 · 控制流

图 3 \xb7 Python 控制流结构

4.1 if-elif-else

score = 85
if score >= 90:
    grade = "A"
elif score >= 80:
    grade = "B"
else:
    grade = "F"
TIP

📌 Python 用缩进定义代码块,不用花括号。Guido 的理由:反正所有人都要缩进,花括号是冗余的。结果:Python 代码天然整齐。

# 条件表达式(三元运算符)
status = "成年" if age >= 18 else "未成年"

4.2 for 循环——遍历可迭代对象

for item in [1, 2, 3]:
    print(item)

for i in range(5):        # 0, 1, 2, 3, 4
    print(i)

# enumerate——同时取索引和值
for i, item in enumerate(["a", "b", "c"]):
    print(f"{i}: {item}")

# zip——并行遍历
for name, age in zip(["Alice", "Bob"], [25, 30]):
    print(f"{name} {age}岁")
TIP

📌 range() 是惰性序列,几乎不占内存。Python 的 for 是"遍历可迭代对象",比 C++ 三段式更安全。需要索引用 enumerate() 而不是 range(len())。

4.3 while 与 while-else

# while-else:循环正常结束(非 break)时执行 else
n = 7
i = 2
while i < n:
    if n % i == 0:
        print(f"{n} 不是质数")
        break
    i += 1
else:
    print(f"{n} 是质数")

4.4 break / continue / pass

for i in range(10):
    if i == 5: break       # 跳出循环
    if i % 2 == 0: continue # 跳过本次
    print(i)               # 1, 3, 5(不打印), 7, 9

def todo(): pass           # pass = 空操作占位符

4.5 match-case 模式匹配(Python 3.10+)

def handle(cmd):
    match cmd.split():
        case ["quit"]: return "退出"
        case ["hello", name]: return f"你好,{name}"
        case ["search", *kw]: return f"搜索:{' '.join(kw)}"
        case _: return "未知"

print(handle("hello Alice"))  # 你好,Alice
TIP

📌 match-case 是结构化模式匹配,能解构列表、字典、对象,比 C++ 的 switch 强大得多。


5 · 列表与元组

图 4 \xb7 Python 函数参数模型

5.1 列表——最常用的可变序列

# 创建
fruits = ["apple", "banana", "cherry"]
mixed = [1, "hello", True, 3.14]     # 可以混合类型
from_range = list(range(5))           # [0, 1, 2, 3, 4]

# 访问(支持负索引)
fruits[0]      # "apple"
fruits[-1]     # "cherry"(倒数第一个)

# 切片 [start:stop:step]
fruits[0:2]    # ["apple", "banana"]
fruits[::-1]   # 反转
fruits[::2]    # 步长2
TIP

📌 切片是 Python 的利器。lst[::-1] 一句反转数组。切片创建新列表——lst[:] 是浅拷贝惯用写法。

列表方法

lst = [3, 1, 4, 1, 5]

# 添加
lst.append(5)          # 末尾添加
lst.insert(0, 0)       # 指定位置插入
lst.extend([7, 8])     # 批量添加

# 删除
lst.remove(1)          # 删除第一个值为 1 的元素
lst.pop()              # 弹出末尾并返回
del lst[0]             # 删除指定位置
lst.clear()            # 清空

# 排序
lst.sort()             # 原地排序(返回 None!)
sorted_lst = sorted(lst)  # 返回新列表,不修改原列表
lst.sort(key=abs)      # 按绝对值排序
lst.sort(reverse=True) # 降序
TIP

📌 sort() vs sorted():lst.sort() 原地修改返回 None;sorted(lst) 返回新列表。原地操作的方法返回 None 是 Python 设计惯例——避免误以为返回了新对象。

列表推导式

squares = [x ** 2 for x in range(10)]
evens = [x for x in range(20) if x % 2 == 0]
labels = ["偶" if x % 2 == 0 else "奇" for x in range(5)]
pairs = [(x, y) for x in range(3) for y in range(3) if x != y]
TIP

📌 列表推导式 = for 循环 + 过滤 + 映射的语法糖。更简洁更快。但不要过度——超过两层嵌套用普通 for 循环更可读。

5.2 元组——不可变序列

point = (3, 4)
single = (42,)        # 单元素必须加逗号!
packed = 1, 2, 3      # 不加括号也行

# 解包
x, y = point
first, *rest = [1, 2, 3, 4]    # first=1, rest=[2, 3, 4]

# 交换变量
a, b = b, a
TIP

📌 元组解包是 Python 的优雅利器。函数返回多个值靠元组:return min(lst), max(lst),调用方 lo, hi = min_max(data)。

为什么有元组还要列表?元组不可变 = 可哈希 = 能做字典键。更轻量。固定含义的值(坐标、RGB)用元组更语义化。


6 · 字典与集合

6.1 字典——键值映射

person = {"name": "Alice", "age": 30}

# 访问
person["name"]              # "Alice"
person.get("phone")         # None(不报错)
person.get("phone", "未填")  # 提供默认值

# 遍历
for k, v in person.items():
    print(f"{k}: {v}")

# 合并(Python 3.9+)
merged = {"a": 1} | {"b": 2}   # {"a": 1, "b": 2}
TIP

📌 字典底层是哈希表,O(1) 查找。键必须是可哈希的(不可变类型)。get() vs []:不确定键存在用 get(),确定存在用 [](fail fast)。

defaultdict

from collections import defaultdict

word_count = defaultdict(int)    # 自动初始化为 0
for word in words:
    word_count[word] += 1

groups = defaultdict(list)       # 自动初始化为空列表
for item in items:
    groups[item["category"]].append(item)

6.2 集合——无序不重复

s = {1, 2, 3, 3}       # {1, 2, 3}(自动去重)

a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
a | b    # 并集 {1,2,3,4,5,6}
a & b    # 交集 {3, 4}
a - b    # 差集 {1, 2}
a ^ b    # 对称差集 {1, 2, 5, 6}

# 去重
unique = list(set(duplicates_list))

# 快速成员判断(O(1) vs list 的 O(n))
3 in a    # True
TIP

📌 集合最大价值:去重和 O(1) 查找。10 万元素 in set 比 in list 快可达 10000 倍。


7 · 字符串深入

7.1 f-string 格式化

name = "Alice"
age = 30
print(f"我叫{name},今年{age}岁")
print(f"{name:*^20}")        # 居中对齐
print(f"{3.14159:.2f}")      # 3.14
print(f"{255:#x}")           # 0xff

path = r"C:\Users\name"      # 原始字符串,不转义

7.2 常用方法

s = "  Hello, World!  "
s.strip()              # "Hello, World!"
s.lower() / s.upper()
s.find("World")        # 9(找不到返回 -1)
s.replace("World", "Python")
s.split(", ")          # ["  Hello", "World!  "]
",".join(["a", "b"])   # "a,b"
"123".isdigit()        # True
"hi".startswith("h")   # True
TIP

📌 split() 和 join() 是逆操作。join 是字符串方法而非列表方法——因为分隔符是字符串,职责放在字符串上更合理。

7.3 编码

s = "你好世界"
b = s.encode("utf-8")    # str → bytes
s2 = b.decode("utf-8")   # bytes → str
TIP

📌 str 是 Unicode 文本,bytes 是字节序列。网络传输/文件读写需要编码解码。乱码 = 编码解码用了不同规则。


8 · 函数基础

8.1 定义与参数

def greet(name, greeting="你好"):
    """向某人打招呼"""
    return f"{greeting},{name}!"

greet("Alice")             # 你好,Alice!
greet("Bob", "Hi")         # Hi,Bob!
greet(name="Carol")        # 你好,Carol!

8.2 *args 和 **kwargs

def func(*args, **kwargs):
    print(f"位置参数: {args}")    # 元组
    print(f"关键字参数: {kwargs}") # 字典

func(1, 2, 3, x=10, y=20)
# 位置参数: (1, 2, 3)
# 关键字参数: {'x': 10, 'y': 20}
TIP

📌 参数排列顺序:位置参数 → 默认参数 → *args → 关键字参数 → **kwargs。Python 3.8+ 用 / 标记仅位置参数,* 标记仅关键字参数。

8.3 Lambda 表达式

# 匿名函数,适合简单的单行操作
square = lambda x: x ** 2
sorted(lst, key=lambda x: x["age"])
list(map(lambda x: x * 2, [1, 2, 3]))
TIP

📌 Lambda 是"用完即扔"的函数。适合 key= 参数和 map/filter。复杂逻辑用 def——lambda 不能有语句、不能有注释。

8.4 函数是第一类对象

# 函数可以赋值、传参、返回
def shout(text): return text.upper()
 whisper = lambda t: t.lower()

funcs = [shout, whisper]
for f in funcs:
    print(f("Hello"))

# 高阶函数
def apply_twice(func, x):
    return func(func(x))

apply_twice(lambda x: x + 3, 10)   # 16

9 · 作用域与闭包

图 5 \xb7 Python 作用域 LEGB 模型

9.1 LEGB 规则

Python 查找变量按 L → E → G → B 顺序:

  • Local:当前函数内部
  • Enclosing:外层嵌套函数(闭包)
  • Global:模块级全局变量
  • Built-in:内置命名空间(print、len 等)
x = "global"          # G
def outer():
    x = "enclosing"   # E
    def inner():
        x = "local"   # L
        print(x)      # → "local"
    inner()
    print(x)          # → "enclosing"
outer()
print(x)              # → "global"

9.2 global 和 nonlocal

count = 0
def increment():
    global count      # 声明使用全局变量
    count += 1

def make_counter():
    n = 0
    def counter():
        nonlocal n    # 声明使用外层函数变量
        n += 1
        return n
    return counter
TIP

📌 global 修改全局变量,nonlocal 修改闭包变量。两者都是"打破默认只读"的声明。能不用就不用——它们让函数有副作用,难以测试。

9.3 闭包

def make_multiplier(factor):
    def multiply(n):
        return n * factor    # factor 被"记住"了
    return multiply

double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5))    # 10
print(triple(5))    # 15
TIP

📌 闭包 = 函数 + 它记住的外层变量。make_multiplier 返回后 factor 本该销毁,但闭包"捕获"了它。这是装饰器的基础。


10 · 装饰器

图 6 \xb7 Python 装饰器原理

10.1 无参数装饰器

import functools

def log_calls(func):
    @functools.wraps(func)    # 保留原函数元信息
    def wrapper(*args, **kwargs):
        print(f"调用 {func.__name__}({args}, {kwargs})")
        result = func(*args, **kwargs)
        print(f"返回 {result}")
        return result
    return wrapper

@log_calls
def add(a, b):
    return a + b

add(3, 4)
# 调用 add((3, 4), {})
# 返回 7
TIP

📌 @decorator 是语法糖,等价于 func = decorator(func)。装饰器本质是"接收函数返回函数"的高阶函数。务必加 @functools.wraps——否则原函数的 __name__、__doc__ 会丢失。

10.2 带参数装饰器

def repeat(times):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for _ in range(times - 1):
                func(*args, **kwargs)
            return func(*args, **kwargs)
        return wrapper
    return decorator

@repeat(3)
def say_hi(name):
    print(f"Hi {name}")

say_hi("Alice")    # 打印 3 次
TIP

📌 带参数装饰器 = 三层嵌套。口诀:无参数两层,有参数三层。最外层接收参数,中间层接收函数,最内层执行逻辑。

10.3 常用内置装饰器

# @property — 把方法变成属性
class Temperature:
    @property
    def celsius(self):
        return self._c

    @celsius.setter
    def celsius(self, value):
        self._c = value

# @staticmethod / @classmethod
class MyClass:
    @staticmethod
    def utility():
        return "不需要 self/cls"

    @classmethod
    def from_dict(cls, d):
        return cls(**d)

# @dataclass(Python 3.7+)
from dataclasses import dataclass

@dataclass
class Point:
    x: float
    y: float

11 · 迭代器与生成器

图 7 \xb7 Python 迭代器与生成器

11.1 迭代器协议

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

class CountUp:
    def __init__(self, limit):
        self.limit = limit
        self.current = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.current >= self.limit:
            raise StopIteration
        self.current += 1
        return self.current

for n in CountUp(5):
    print(n)    # 1, 2, 3, 4, 5
TIP

📌 for 循环的本质:for x in obj 等价于 obj = iter(obj); while True: try: x = next(obj) except StopIteration: break。理解了这个,迭代器就不神秘了。

11.2 生成器函数——yield

def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

fib = fibonacci()
for _ in range(10):
    print(next(fib))    # 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
TIP

📌 yield 让函数"暂停"。执行到 yield 时返回值并暂停,next() 时从暂停处继续。局部变量保留——这是和普通函数最大的区别。无限序列不占内存。

11.3 生成器表达式

# 列表推导:立即创建全部元素,占 O(n) 内存
squares_list = [x ** 2 for x in range(1000000)]

# 生成器表达式:惰性求值,占 O(1) 内存
squares_gen = (x ** 2 for x in range(1000000))

print(next(squares_gen))   # 0
print(next(squares_gen))   # 1

# 求和不需要先建列表
total = sum(x ** 2 for x in range(1000000))   # 高效!
TIP

📌 列表 vs 生成器的选择:需要多次遍历/索引/len() → 列表。只需遍历一次/数据量大 → 生成器。类比:列表=全做好放桌上,生成器=流水线要一个做一个。


12 · 类与对象

图 8 \xb7 Python OOP 模型

12.1 类的定义

class Dog:
    species = "Canis familiaris"    # 类变量(所有实例共享)

    def __init__(self, name, age):  # 构造函数
        self.name = name            # 实例变量(每个实例独有)
        self.age = age

    def bark(self):                 # 实例方法
        return f"{self.name} says Woof!"

    @classmethod
    def from_dict(cls, d):          # 类方法(工厂模式)
        return cls(d["name"], d["age"])

    @staticmethod
    def info():                     # 静态方法(逻辑归属类)
        return "Dogs are loyal"

dog = Dog("Buddy", 3)
print(dog.bark())          # Buddy says Woof!
print(Dog.from_dict({"name": "Max", "age": 5}).bark())
TIP

📌 三种方法的区别:实例方法(self→实例)、类方法(cls→类,工厂模式用)、静态方法(无 self/cls,逻辑上属于类但不访问实例/类数据)。

12.2 @property

class Temperature:
    def __init__(self, celsius):
        self._celsius = celsius

    @property
    def fahrenheit(self):
        return self._celsius * 9 / 5 + 32

    @fahrenheit.setter
    def fahrenheit(self, f):
        self._celsius = (f - 32) * 5 / 9

t = Temperature(100)
print(t.fahrenheit)    # 212.0(像属性一样访问)
t.fahrenheit = 32      # 像属性一样赋值
print(t._celsius)      # 0.0

13 · 继承与多态

13.1 单继承

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

    def speak(self):
        raise NotImplementedError("子类必须实现")

class Dog(Animal):
    def speak(self):
        return f"{self.name} says Woof!"

class Cat(Animal):
    def speak(self):
        return f"{self.name} says Meow!"

animals = [Dog("Buddy"), Cat("Whiskers")]
for a in animals:
    print(a.speak())    # 多态:同一接口不同行为

13.2 鸭子类型

class Robot:
    def speak(self):
        return "Beep boop"

# Robot 不继承 Animal,但有 speak() 方法
animals = [Dog("Buddy"), Cat("Whiskers"), Robot()]
for a in animals:
    print(a.speak())    # 都能调用!
TIP

📌 鸭子类型:"如果它走起来像鸭子、叫起来像鸭子,那它就是鸭子。"Python 不关心类型,只关心行为(有没有对应方法)。这就是 Python 多态的本质——不需要继承关系。

13.3 多继承与 MRO

class A:
    def greet(self): return "A"

class B(A):
    def greet(self): return "B"

class C(A):
    def greet(self): return "C"

class D(B, C):
    pass

print(D().greet())         # B(MRO: D → B → C → A)
print(D.__mro__)           # 查看方法解析顺序
TIP

📌 Python 用 C3 线性化算法确定 MRO。多继承复杂时用 cls.__mro__ 查看顺序。super() 不是"调用父类",而是"调用 MRO 中的下一个"。


14 · 魔术方法

14.1 运算符重载

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __add__(self, other):       # +
        return Vector(self.x + other.x, self.y + other.y)

    def __eq__(self, other):        # ==
        return self.x == other.x and self.y == other.y

    def __repr__(self):             # repr(),面向开发者
        return f"Vector({self.x}, {self.y})"

    def __str__(self):              # str()/print(),面向用户
        return f"({self.x}, {self.y})"

v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2)    # (4, 6)
print(repr(v1))   # Vector(1, 2)

14.2 容器协议

class Matrix:
    def __init__(self, data):
        self.data = data

    def __getitem__(self, key):     # obj[key]
        return self.data[key]

    def __len__(self):              # len(obj)
        return len(self.data)

    def __contains__(self, item):   # item in obj
        return any(item in row for row in self.data)

    def __iter__(self):             # for x in obj
        for row in self.data:
            yield from row

14.3 上下文管理器

class FileManager:
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode

    def __enter__(self):
        self.file = open(self.filename, self.mode)
        return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.file.close()
        return False    # 不吞异常

with FileManager("test.txt", "w") as f:
    f.write("Hello")    # 退出 with 块自动关闭
TIP

📌 with 语句 = try-finally 的语法糖。__enter__ 进入时执行,__exit__ 退出时执行(无论有无异常)。用于资源管理(文件、锁、数据库连接)。


15 · 数据类与类型提示

15.1 dataclass

from dataclasses import dataclass, field

@dataclass
class Student:
    name: str
    age: int
    scores: list = field(default_factory=list)  # 可变默认值用 field

    def average(self):
        return sum(self.scores) / len(self.scores) if self.scores else 0

s = Student("Alice", 20, [90, 85, 92])
print(s)               # Student(name='Alice', age=20, scores=[90, 85, 92])
print(s.average())     # 89.0
TIP

📌 @dataclass 自动生成 __init__、__repr__、__eq__。省去样板代码。可变默认值必须用 field(default_factory=...)——直接用 [] 会触发共享陷阱。

15.2 类型提示

from typing import Optional, Union, List, Dict, Callable

def greet(name: str, times: int = 1) -> str:
    return (f"Hello {name}! ") * times

def find(items: list[int], target: int) -> int | None:
    for i, item in enumerate(items):
        if item == target:
            return i
    return None

# Python 3.9+ 可以直接用 list[int], dict[str, int]
# Python 3.10+ 可以用 X | Y 代替 Union[X, Y]
TIP

📌 类型提示不强制运行时检查——它只是"注释",给 IDE、mypy、pyright 用。Python 哲学:类型提示是工具,不是枷锁。


16 · 异常处理

图 9 \xb7 Python 异常处理体系

16.1 try-except-else-finally

try:
    result = 10 / x
except ZeroDivisionError as e:
    print(f"除零错误: {e}")
except (TypeError, ValueError) as e:
    print(f"类型/值错误: {e}")
except Exception as e:
    print(f"其他异常: {e}")
else:
    print(f"结果: {result}")    # 无异常时执行
finally:
    print("清理资源")            # 总是执行
TIP

📌 捕获顺序:先具体后通用。except Exception 放最后兜底。else 在无异常时执行(适合"成功后的逻辑"),finally 无论有无异常都执行(适合资源清理)。

16.2 自定义异常

class AppError(Exception):
    """应用基础异常"""
    pass

class ValidationError(AppError):
    def __init__(self, field, message):
        self.field = field
        super().__init__(f"{field}: {message}")

class NotFoundError(AppError):
    pass

# 使用
try:
    raise ValidationError("email", "格式不正确")
except ValidationError as e:
    print(e.field)    # email

16.3 raise 和异常链

try:
    data = json.loads(text)
except json.JSONDecodeError as e:
    raise ValueError("配置文件格式错误") from e
    # from e 保留原始异常链,traceback 会显示两者

17 · 文件与 IO

17.1 文件读写

# 推荐用 with 语句(自动关闭)
with open("data.txt", "r", encoding="utf-8") as f:
    content = f.read()          # 一次性读取
    # 或逐行
    for line in f:
        print(line.strip())

with open("output.txt", "w", encoding="utf-8") as f:
    f.write("Hello\n")
    f.writelines(["line1\n", "line2\n"])
TIP

📌 永远指定 encoding="utf-8"。Windows 默认 GBK,Linux 默认 UTF-8——不指定会跨平台乱码。with 语句保证文件一定关闭,即使中间出异常。

17.2 pathlib——面向路径

from pathlib import Path

p = Path("docs") / "notes" / "readme.md"
p.exists()         # True/False
p.is_file()
p.parent           # Path("docs/notes")
p.suffix           # ".md"
p.stem             # "readme"
p.read_text(encoding="utf-8")     # 一行读
p.write_text("content", encoding="utf-8")

# 遍历
for f in Path(".").rglob("*.py"):
    print(f)
TIP

📌 pathlib 比 os.path 更优雅。Path("a") / "b" / "c" 比 os.path.join("a", "b", "c") 可读多了。Python 3.4+ 推荐 pathlib。

17.3 json / csv / pickle

import json, csv, pickle

# JSON
data = {"name": "Alice", "scores": [90, 85]}
json.dumps(data)              # 序列化为字符串
json.loads(json_str)          # 反序列化
with open("d.json", "w") as f:
    json.dump(data, f, ensure_ascii=False, indent=2)

# CSV
with open("data.csv") as f:
    reader = csv.DictReader(f)
    for row in reader:
        print(row["name"], row["age"])

# pickle(仅 Python 内部,有安全风险)
with open("data.pkl", "wb") as f:
    pickle.dump(obj, f)
WARNING

🚧 pickle 不安全——加载恶意 pickle 可以执行任意代码。永远不要 pickle.loads 不信任的来源。跨语言交换数据用 JSON。


18 · 模块与包

18.1 模块

# mymath.py
def add(a, b): return a + b
PI = 3.14159

# main.py
import mymath
print(mymath.add(1, 2))     # 3
print(mymath.PI)

from mymath import add, PI  # 导入特定名称
from mymath import *        # 导入全部(不推荐)
import mymath as mm         # 别名

18.2 包

myproject/ ├── __init__.py # 标记为包(Python 3.3+ 可省略) ├── utils/ │ ├── __init__.py │ ├── string_utils.py │ └── file_utils.py └── main.py
from utils.string_utils import capitalize
from utils import file_utils as fu
TIP

📌 __init__.py 的作用:标记目录为包,可以控制 from package import * 导出什么。空文件也可以。Python 3.3+ 有命名空间包,不需要 __init__.py 也能导入。

18.3 if __name__ == "__main__"

# module.py
def main():
    print("运行主程序")

if __name__ == "__main__":
    main()
TIP

📌 这个判断让模块既能被 import 又能直接运行。import 时 __name__ 是模块名,直接运行时是 "__main__"。Python 的惯用模式。


19 · 虚拟环境与包管理

19.1 venv

# 创建虚拟环境
python -m venv .venv

# 激活
# Linux/macOS
source .venv/bin/activate
# Windows
.venv\Scripts\activate

# 安装包
pip install requests
pip install -r requirements.txt

# 导出依赖
pip freeze > requirements.txt

19.2 现代工具链

# uv — 极快的包管理器(Rust 编写)
uv venv
uv pip install requests
uv pip compile requirements.in -o requirements.txt

# poetry — 项目级依赖管理
poetry init
poetry add requests
poetry install

# ruff — 极快的 linter + formatter(替代 flake8 + black)
ruff check .
ruff format .
TIP

📌 为什么需要虚拟环境?不同项目依赖不同版本的包,全局安装会冲突。虚拟环境给每个项目独立的包环境。uv 是 2024 年的新星——比 pip 快 10-100x。


20 · 多线程

图 10 \xb7 Python 并发编程对比

20.1 threading 基础

import threading

def download(url):
    # 模拟 I/O 操作
    print(f"下载 {url}")

threads = []
for url in ["a.com", "b.com", "c.com"]:
    t = threading.Thread(target=download, args=(url,))
    threads.append(t)
    t.start()

for t in threads:
    t.join()    # 等待所有线程完成

20.2 GIL——全局解释器锁

# GIL 保证同一时刻只有一个线程执行 Python 字节码
# 这意味着:多线程在 CPU 密集型任务中不会加速!

# CPU 密集型:多线程反而更慢(线程切换开销)
def cpu_work():
    total = sum(i * i for i in range(10**7))
    return total

# I/O 密集型:多线程有效(等待 I/O 时释放 GIL)
def io_work(url):
    response = requests.get(url)    # 等 I/O 时其他线程可以运行
    return response.text
TIP

📌 GIL 是 CPython 的历史包袱。因为 Python 引用计数 GC 不是线程安全的,用 GIL 做最简单的保证。结果:多线程不能利用多核做 CPU 计算,但 I/O 密集型场景仍然有效。Python 3.13 有实验性 no-GIL 模式。

20.3 线程安全

import threading

# Lock — 互斥锁
lock = threading.Lock()
counter = 0

def safe_increment():
    global counter
    with lock:          # 自动获取/释放
        counter += 1

# 不加锁的并发写会有竞态条件!
threads = [threading.Thread(target=safe_increment) for _ in range(1000)]
for t in threads: t.start()
for t in threads: t.join()
print(counter)    # 1000(加锁后正确)

21 · 多进程

from multiprocessing import Process, Pool

def square(n):
    return n * n

if __name__ == "__main__":
    # 单进程
    p = Process(target=square, args=(5,))
    p.start()
    p.join()

    # 进程池(推荐)
    with Pool(4) as pool:
        results = pool.map(square, range(10))
        print(results)    # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
TIP

📌 多进程绕过 GIL——每个进程有独立的 Python 解释器和 GIL。CPU 密集型任务用 multiprocessing。代价:进程创建开销大,进程间通信(IPC)需要 Queue/Pipe。


22 · 异步编程 asyncio

import asyncio

async def fetch_data(url):
    print(f"开始获取 {url}")
    await asyncio.sleep(1)     # 模拟异步 I/O
    print(f"完成 {url}")
    return f"data from {url}"

async def main():
    # 并发执行多个协程
    results = await asyncio.gather(
        fetch_data("a.com"),
        fetch_data("b.com"),
        fetch_data("c.com"),
    )
    print(results)    # 3 个请求总共约 1 秒(而非 3 秒)

asyncio.run(main())
TIP

📌 asyncio 是单线程并发。async/await 让出一个协程去执行另一个,不需要线程切换。适合海量 I/O 并发(Web 服务器、爬虫)。但 await 只能用在 async 函数中,且需要异步库支持(aiohttp 而非 requests)。

asyncio vs 多线程 vs 多进程

维度 多线程 多进程 asyncio
并行度 并发不并行(GIL) 真正并行 并发不并行
切换开销 小(~KB) 大(~MB) 极小(~B)
内存 共享 独立 共享(单线程)
最佳场景 少量 I/O CPU 密集 海量 I/O

23 · 标准库精选

23.1 collections

from collections import Counter, defaultdict, deque, namedtuple

# Counter — 计数器
words = "the cat sat on the mat the cat".split()
c = Counter(words)
print(c.most_common(2))    # [('the', 3), ('cat', 2)]

# deque — 双端队列(O(1) 头尾操作)
dq = deque([1, 2, 3])
dq.appendleft(0)    # O(1)
dq.popleft()        # O(1)(list.pop(0) 是 O(n)!)

# namedtuple — 不可变+字段名
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
print(p.x, p.y)     # 3 4

23.2 itertools

from itertools import chain, combinations, permutations, product

# chain — 拼接多个可迭代对象
for x in chain([1, 2], [3, 4]):
    print(x)    # 1, 2, 3, 4

# combinations / permutations
list(combinations("ABC", 2))    # [('A','B'), ('A','C'), ('B','C')]
list(permutations("ABC", 2))    # 6 个排列

# product — 笛卡尔积
for a, b in product("AB", "12"):
    print(a + b)    # A1, A2, B1, B2

23.3 functools / operator

from functools import lru_cache, partial, reduce
from operator import itemgetter, attrgetter

# lru_cache — 自动缓存函数结果
@lru_cache(maxsize=128)
def fib(n):
    if n < 2: return n
    return fib(n-1) + fib(n-2)

# partial — 固定部分参数
int2 = partial(int, base=2)
int2("1010")    # 10

# itemgetter — 快速取键
sorted(students, key=itemgetter("age"))

# reduce — 累积运算
reduce(lambda a, b: a + b, [1, 2, 3, 4])    # 10

23.4 pathlib / subprocess / logging

import subprocess, logging

# subprocess
result = subprocess.run(["ls", "-la"], capture_output=True, text=True)
print(result.stdout)

# logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logging.info("启动服务")
logging.error("连接失败")

24 · 测试入门

24.1 unittest

import unittest

def add(a, b): return a + b

class TestAdd(unittest.TestCase):
    def test_basic(self):
        self.assertEqual(add(1, 2), 3)

    def test_negative(self):
        self.assertEqual(add(-1, 1), 0)

    def setUp(self):
        print("每个测试前执行")

if __name__ == "__main__":
    unittest.main()

24.2 pytest(推荐)

# test_math.py
def test_add():
    assert add(1, 2) == 3
    assert add(-1, 1) == 0

# 参数化测试
import pytest

@pytest.mark.parametrize("a, b, expected", [
    (1, 2, 3),
    (-1, 1, 0),
    (0, 0, 0),
])
def test_add_cases(a, b, expected):
    assert add(a, b) == expected

# fixture
@pytest.fixture
def sample_list():
    return [1, 2, 3, 4, 5]

def test_sum(sample_list):
    assert sum(sample_list) == 15
$ pytest test_math.py -v
$ pytest --cov=myproject    # 覆盖率
TIP

📌 pytest 比 unittest 简洁。assert 直接用,不需要 self.assertEqual。fixture 比 setUp/tearDown 灵活。@parametrize 让数据驱动测试更优雅。


25 · 性能优化

25.1 先测量再优化

import time

# timeit — 精确计时
import timeit
timeit.timeit("''.join(['a']*1000)", number=10000)

# cProfile — 找瓶颈
import cProfile
cProfile.run('my_function()')

# 时间测量装饰器
def timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        print(f"{func.__name__}: {time.perf_counter() - start:.4f}s")
        return result
    return wrapper
TIP

📌 "过早优化是万恶之源"——Knuth。Python 慢的地方通常只占 10% 代码。用 cProfile 找到瓶颈,只优化那 10%。大部分时候 Python 够快。

25.2 常见优化技巧

# 1. 字符串拼接用 join 而非 +=
parts = []
for word in words:
    parts.append(word)
result = " ".join(parts)    # O(n)
# 而非 result += word       # O(n²)!

# 2. 成员判断用 set 而非 list
valid = set(valid_ids)      # O(1) 查找
# 而非 valid = valid_ids    # O(n) 查找

# 3. 用局部变量(比全局快)
def loop():
    local_len = len    # 局部引用更快
    for item in data:
        local_len(item)

# 4. 列表推导比 for 循环快
squares = [x**2 for x in range(1000)]   # 比循环 append 快

# 5. 用生成器处理大数据
total = sum(x**2 for x in range(10**8))  # 不建大列表

25.3 真正需要性能时

# 1. NumPy — 向量化计算
import numpy as np
arr = np.arange(1000000)
result = arr * 2    # C 层面并行,比 Python 循环快 100x

# 2. Cython — 编译 Python 为 C
# 3. ctypes / cffi — 调用 C 库
# 4. multiprocessing — 多核并行
# 5. PyPy — 替代解释器(JIT 编译)
TIP

📌 Python 性能策略:先写对 → 测量找瓶颈 → 优化算法 → 用 NumPy/内置函数 → 最后才考虑 Cython/C 扩展。90% 的性能问题靠"换算法"解决。


附录 A · 内置函数速查

函数 说明 示例
len(x) 长度 len([1,2,3]) → 3
range(n) 整数序列 list(range(3)) → [0,1,2]
enumerate(x) 索引+值 for i, v in enumerate(lst)
zip(a, b) 并行遍历 for x, y in zip(a, b)
map(f, x) 映射 map(str, [1,2])
filter(f, x) 过滤 filter(None, [0,1,2])
sorted(x) 排序 sorted(lst, key=..., reverse=...)
reversed(x) 反转 list(reversed([1,2,3]))
sum(x) 求和 sum([1,2,3]) → 6
any(x) 任一为真 any([0, 0, 1]) → True
all(x) 全部为真 all([1, 1, 0]) → False
min/max(x) 最小/最大 max([3,1,2]) → 3
type(x) 类型 type(42) → <class 'int'>
isinstance(x, T) 类型检查 isinstance(42, int) → True
id(x) 对象标识 id(obj) → 内存地址
dir(x) 所有属性 dir(str)
getattr(x, 'n') 动态取属性 getattr(obj, 'name')
hasattr(x, 'n') 有无属性 hasattr(obj, 'name')
repr(x) 开发者字符串 repr(obj)
frozenset(x) 不可变集合 frozenset({1,2,3})

附录 B · 常见陷阱与最佳实践

B.1 可变默认值陷阱

# 错误
def add_item(item, lst=[]):
    lst.append(item)
    return lst

add_item(1)    # [1]
add_item(2)    # [1, 2]!不是 [2]!默认值只创建一次

# 正确
def add_item(item, lst=None):
    if lst is None:
        lst = []
    lst.append(item)
    return lst

B.2 闭包延迟绑定

# 陷阱
funcs = [lambda: i for i in range(3)]
print([f() for f in funcs])    # [2, 2, 2]!不是 [0, 1, 2]

# 修复:用默认参数捕获当前值
funcs = [lambda i=i: i for i in range(3)]
print([f() for f in funcs])    # [0, 1, 2]

B.3 浅拷贝 vs 深拷贝

import copy

original = [[1, 2], [3, 4]]

# 浅拷贝:外层新列表,内层共享
shallow = original.copy()
shallow[0][0] = 99
print(original)    # [[99, 2], [3, 4]]!内层被改了

# 深拷贝:完全独立
deep = copy.deepcopy(original)
deep[0][0] = 0
print(original)    # 不受影响

B.4 == vs is

# == 比较值,is 比较身份
a = [1, 2]; b = [1, 2]
a == b    # True(值相等)
a is b    # False(不同对象)

# 判断 None 永远用 is
if x is None: ...    # 正确
if x == None: ...    # 错误(可能被 __eq__ 重载)

B.5 最佳实践清单

  • 用 with 管理资源(文件、锁、连接)
  • 用 pathlib 代替 os.path
  • 用 f-string 代替 %/.format()
  • 用 enumerate 代替 range(len())
  • 用 dataclass 代替手写 __init__
  • 类型提示 + mypy 检查
  • pytest 测试 + ruff 检查
  • 虚拟环境隔离项目依赖
  • if __name__ == "__main__" 保护入口

附录 C · 实战项目

C.1 命令行工具(argparse + pathlib)

import argparse
from pathlib import Path

def main():
    parser = argparse.ArgumentParser(description="统计代码行数")
    parser.add_argument("path", help="目录路径")
    parser.add_argument("--ext", default=".py", help="文件扩展名")
    args = parser.parse_args()

    total = 0
    for f in Path(args.path).rglob(f"*{args.ext}"):
        lines = len(f.read_text(encoding="utf-8").splitlines())
        print(f"{f}: {lines} 行")
        total += lines
    print(f"总计: {total} 行")

if __name__ == "__main__":
    main()

C.2 异步爬虫(asyncio + aiohttp)

import asyncio
import aiohttp

async def fetch(session, url):
    async with session.get(url) as resp:
        return await resp.text()

async def main():
    urls = ["https://example.com" for _ in range(10)]
    async with aiohttp.ClientSession() as session:
        results = await asyncio.gather(*[fetch(session, u) for u in urls])
        for r in results:
            print(len(r), "字符")

asyncio.run(main())

C.3 数据处理管道(生成器 + itertools)

from itertools import islice

def read_large_file(path):
    """逐行读取大文件(生成器)"""
    with open(path, encoding="utf-8") as f:
        for line in f:
            yield line.strip()

def parse(lines):
    """解析每行"""
    for line in lines:
        yield line.split(",")

def filter_valid(rows):
    """过滤有效行"""
    for row in rows:
        if len(row) >= 3:
            yield row

# 管道:读取 → 解析 → 过滤 → 取前100
pipeline = islice(filter_valid(parse(read_large_file("data.csv"))), 100)
for row in pipeline:
    print(row)
TIP

📌 生成器管道是处理大数据的利器。每一步都是惰性的,数据像流水线一样流过,不需要把全部数据加载到内存。这就是 Unix 哲学的 Python 版——小工具组合成强大管道。

本页目录