一份完整的 Python 学习笔记。学多少记多少,重点讲"为什么这么设计"(费曼式 📌 讲解),覆盖从入门到进阶的完整知识体系。
图表在
/Python学习笔记/diagrams/目录。图注名即原图文件名,看图注就能直接找到原图用 draw.io 编辑。
import this)说:"Simple is better than complex".pyc 字节码缓存)📌 打个比方:C++ 像手动挡赛车——你控制一切(内存、类型、编译),性能极致但容易熄火。Python 像自动挡轿车——帮你管好油离配合(内存、类型),你专注路线(逻辑),虽然少了一点极致性能,但到达目的地更快更轻松。
| 特性 | Python | C++ |
|---|---|---|
| 类型系统 | 动态类型 | 静态类型 |
| 内存管理 | 自动 GC | 手动 / RAII |
| 编译方式 | 解释执行(字节码) | 编译为机器码 |
| 语法复杂度 | 低(缩进定义块) | 高(花括号 + 头文件) |
| 运行速度 | 慢(约 10-100x) | 快 |
| 适用场景 | 脚本/AI/Web/数据 | 系统/游戏/嵌入式/高性能 |
📌 为什么 Python 慢还能流行?因为开发效率 > 运行效率。大多数程序的时间不是花在 CPU 计算上,而是在等 I/O(网络、磁盘)。Python 的慢在 I/O 密集型场景几乎无感。而真正需要性能的地方(如 NumPy、PyTorch),底层都是 C/C++ 实现的,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 年停止维护,不要再用。
📌 REPL 是 Python 的杀手锏。C++ 改一行代码要等编译,Python 在 REPL 里敲一行立刻看结果。>>> help(str.split) 立刻看文档,>>> dir(obj) 看所有方法。
📌 关键理解:Python 变量不是"装值的盒子",而是"贴在对象上的标签"。b = a 不是复制盒子,而是给同一个对象再贴一个标签。a = 20 是把 a 标签撕下来贴到新对象 20 上,b 还贴在 10 上。
📌 为什么一切皆对象?这简化了语言设计——不需要区分"基本类型"和"对象类型",所有变量都遵循同一套规则。对比 Java 的 int vs Integer,Python 少了一整层心智负担。
| 类型 | 示例 | 可变? | 说明 |
|---|---|---|---|
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 |
不可变 | 空值 |
📌 为什么 bool 继承 int?历史原因。Python 早期没有 bool,用 0/1 表示真假。后来加 bool 时为了向后兼容,让 True=1、False=0。这也是为什么 sum([True, False, True]) 返回 2。
📌 打个比方:不可变类型像刻在石头上的字——想改只能换新石头。可变类型像白板——随时擦了重写,白板还是那块。
为什么重要?它解释了函数传参时可变默认值的陷阱,也解释了为什么字典的键必须是不可变类型。
🚧 bool("false") 返回 True!任何非空字符串都是 True,不管内容是什么。
📌 is 比较身份(id),== 比较值。规则:判断 None 用 is,其他情况用 ==。
📌 // 是向下取整,不是截断。-7 // 2 = -4(不是 -3!)。C++ 整数除法是截断(向零取整)。需要 C++ 风格用 int(a / b)。
📌 链式比较不仅简洁,还更高效——b 只计算一次。C++ 里只能写 1 < x && x < 10。
📌 and/or 返回的不是 True/False,而是操作数本身。实用模式:name = input_name or "匿名用户"。
:=(Python 3.8+)📌 Python 用缩进定义代码块,不用花括号。Guido 的理由:反正所有人都要缩进,花括号是冗余的。结果:Python 代码天然整齐。
📌 range() 是惰性序列,几乎不占内存。Python 的 for 是"遍历可迭代对象",比 C++ 三段式更安全。需要索引用 enumerate() 而不是 range(len())。
📌 match-case 是结构化模式匹配,能解构列表、字典、对象,比 C++ 的 switch 强大得多。
📌 切片是 Python 的利器。lst[::-1] 一句反转数组。切片创建新列表——lst[:] 是浅拷贝惯用写法。
📌 sort() vs sorted():lst.sort() 原地修改返回 None;sorted(lst) 返回新列表。原地操作的方法返回 None 是 Python 设计惯例——避免误以为返回了新对象。
📌 列表推导式 = for 循环 + 过滤 + 映射的语法糖。更简洁更快。但不要过度——超过两层嵌套用普通 for 循环更可读。
📌 元组解包是 Python 的优雅利器。函数返回多个值靠元组:return min(lst), max(lst),调用方 lo, hi = min_max(data)。
为什么有元组还要列表?元组不可变 = 可哈希 = 能做字典键。更轻量。固定含义的值(坐标、RGB)用元组更语义化。
📌 字典底层是哈希表,O(1) 查找。键必须是可哈希的(不可变类型)。get() vs []:不确定键存在用 get(),确定存在用 [](fail fast)。
📌 集合最大价值:去重和 O(1) 查找。10 万元素 in set 比 in list 快可达 10000 倍。
📌 split() 和 join() 是逆操作。join 是字符串方法而非列表方法——因为分隔符是字符串,职责放在字符串上更合理。
📌 str 是 Unicode 文本,bytes 是字节序列。网络传输/文件读写需要编码解码。乱码 = 编码解码用了不同规则。
📌 参数排列顺序:位置参数 → 默认参数 → *args → 关键字参数 → **kwargs。Python 3.8+ 用 / 标记仅位置参数,* 标记仅关键字参数。
📌 Lambda 是"用完即扔"的函数。适合 key= 参数和 map/filter。复杂逻辑用 def——lambda 不能有语句、不能有注释。
Python 查找变量按 L → E → G → B 顺序:
print、len 等)📌 global 修改全局变量,nonlocal 修改闭包变量。两者都是"打破默认只读"的声明。能不用就不用——它们让函数有副作用,难以测试。
📌 闭包 = 函数 + 它记住的外层变量。make_multiplier 返回后 factor 本该销毁,但闭包"捕获"了它。这是装饰器的基础。
📌 @decorator 是语法糖,等价于 func = decorator(func)。装饰器本质是"接收函数返回函数"的高阶函数。务必加 @functools.wraps——否则原函数的 __name__、__doc__ 会丢失。
📌 带参数装饰器 = 三层嵌套。口诀:无参数两层,有参数三层。最外层接收参数,中间层接收函数,最内层执行逻辑。
📌 for 循环的本质:for x in obj 等价于 obj = iter(obj); while True: try: x = next(obj) except StopIteration: break。理解了这个,迭代器就不神秘了。
📌 yield 让函数"暂停"。执行到 yield 时返回值并暂停,next() 时从暂停处继续。局部变量保留——这是和普通函数最大的区别。无限序列不占内存。
📌 列表 vs 生成器的选择:需要多次遍历/索引/len() → 列表。只需遍历一次/数据量大 → 生成器。类比:列表=全做好放桌上,生成器=流水线要一个做一个。
📌 三种方法的区别:实例方法(self→实例)、类方法(cls→类,工厂模式用)、静态方法(无 self/cls,逻辑上属于类但不访问实例/类数据)。
📌 鸭子类型:"如果它走起来像鸭子、叫起来像鸭子,那它就是鸭子。"Python 不关心类型,只关心行为(有没有对应方法)。这就是 Python 多态的本质——不需要继承关系。
📌 Python 用 C3 线性化算法确定 MRO。多继承复杂时用 cls.__mro__ 查看顺序。super() 不是"调用父类",而是"调用 MRO 中的下一个"。
📌 with 语句 = try-finally 的语法糖。__enter__ 进入时执行,__exit__ 退出时执行(无论有无异常)。用于资源管理(文件、锁、数据库连接)。
📌 @dataclass 自动生成 __init__、__repr__、__eq__。省去样板代码。可变默认值必须用 field(default_factory=...)——直接用 [] 会触发共享陷阱。
📌 类型提示不强制运行时检查——它只是"注释",给 IDE、mypy、pyright 用。Python 哲学:类型提示是工具,不是枷锁。
📌 捕获顺序:先具体后通用。except Exception 放最后兜底。else 在无异常时执行(适合"成功后的逻辑"),finally 无论有无异常都执行(适合资源清理)。
📌 永远指定 encoding="utf-8"。Windows 默认 GBK,Linux 默认 UTF-8——不指定会跨平台乱码。with 语句保证文件一定关闭,即使中间出异常。
📌 pathlib 比 os.path 更优雅。Path("a") / "b" / "c" 比 os.path.join("a", "b", "c") 可读多了。Python 3.4+ 推荐 pathlib。
🚧 pickle 不安全——加载恶意 pickle 可以执行任意代码。永远不要 pickle.loads 不信任的来源。跨语言交换数据用 JSON。
📌 __init__.py 的作用:标记目录为包,可以控制 from package import * 导出什么。空文件也可以。Python 3.3+ 有命名空间包,不需要 __init__.py 也能导入。
if __name__ == "__main__"📌 这个判断让模块既能被 import 又能直接运行。import 时 __name__ 是模块名,直接运行时是 "__main__"。Python 的惯用模式。
📌 为什么需要虚拟环境?不同项目依赖不同版本的包,全局安装会冲突。虚拟环境给每个项目独立的包环境。uv 是 2024 年的新星——比 pip 快 10-100x。
📌 GIL 是 CPython 的历史包袱。因为 Python 引用计数 GC 不是线程安全的,用 GIL 做最简单的保证。结果:多线程不能利用多核做 CPU 计算,但 I/O 密集型场景仍然有效。Python 3.13 有实验性 no-GIL 模式。
📌 多进程绕过 GIL——每个进程有独立的 Python 解释器和 GIL。CPU 密集型任务用 multiprocessing。代价:进程创建开销大,进程间通信(IPC)需要 Queue/Pipe。
📌 asyncio 是单线程并发。async/await 让出一个协程去执行另一个,不需要线程切换。适合海量 I/O 并发(Web 服务器、爬虫)。但 await 只能用在 async 函数中,且需要异步库支持(aiohttp 而非 requests)。
| 维度 | 多线程 | 多进程 | asyncio |
|---|---|---|---|
| 并行度 | 并发不并行(GIL) | 真正并行 | 并发不并行 |
| 切换开销 | 小(~KB) | 大(~MB) | 极小(~B) |
| 内存 | 共享 | 独立 | 共享(单线程) |
| 最佳场景 | 少量 I/O | CPU 密集 | 海量 I/O |
📌 pytest 比 unittest 简洁。assert 直接用,不需要 self.assertEqual。fixture 比 setUp/tearDown 灵活。@parametrize 让数据驱动测试更优雅。
📌 "过早优化是万恶之源"——Knuth。Python 慢的地方通常只占 10% 代码。用 cProfile 找到瓶颈,只优化那 10%。大部分时候 Python 够快。
📌 Python 性能策略:先写对 → 测量找瓶颈 → 优化算法 → 用 NumPy/内置函数 → 最后才考虑 Cython/C 扩展。90% 的性能问题靠"换算法"解决。
| 函数 | 说明 | 示例 |
|---|---|---|
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}) |
with 管理资源(文件、锁、连接)pathlib 代替 os.pathf-string 代替 %/.format()enumerate 代替 range(len())dataclass 代替手写 __init__mypy 检查pytest 测试 + ruff 检查if __name__ == "__main__" 保护入口📌 生成器管道是处理大数据的利器。每一步都是惰性的,数据像流水线一样流过,不需要把全部数据加载到内存。这就是 Unix 哲学的 Python 版——小工具组合成强大管道。