Python 进阶知识体系:2
Python 进阶知识体系2
这篇笔记整理了 Python 字符串格式化、生成器、深浅拷贝、猴子补丁、元类等核心进阶知识点。每个主题都配有场景化记忆法和实战示例,帮你真正理解并记住这些容易混淆的概念。
一、字符串格式化:从 % 到 f-string
1.1 % 格式化的核心语法
'GNU\'s Not %s %%' % 'UNIX' 这句话里,%扮演了两个角色:
%s:占位符,表示"这里要放一个字符串"% 'UNIX':格式化操作符,意思是把'UNIX'填进前面的占位符%%:转义字符,表示输出一个真正的%
执行结果:"GNU's Not UNIX %"
1.2 三种格式化方式对比
| 方式 | 语法 | 适用场景 | 特点 |
|---|---|---|---|
| % 格式化 | 'Hello %s' % name |
老代码兼容 | 语法简单但不够灵活 |
| str.format | 'Hello {}'.format(name) |
Python 3.0+ | 功能强大,支持位置和关键字 |
| f-string | f'Hello {name}' |
Python 3.6+ | 最简洁,性能最优 |
经验之谈:新项目直接用 f-string,老代码遇到 % 格式化也别慌,记住
%s是占位符,%%是转义就行。
1.3 常见占位符速查
'%d' % 42 # 整数
'%f' % 3.14 # 浮点数
'%s' % 'hello' # 字符串
'%x' % 255 # 十六进制
'%.2f' % 3.14159 # 保留两位小数
二、生成器:懒人的"糖果工厂"
2.1 生成器的核心思想
生成器就像一家按需生产的糖果工厂——顾客要一颗,你才做一颗,不提前囤货,节省内存。
def make_candies():
for i in range(1, 4):
yield f"糖果{i}" # 产出一颗,暂停
candy_gen = make_candies() # 创建工厂,但没开始生产
next(candy_gen) # 产出"糖果1"
next(candy_gen) # 产出"糖果2"
next(candy_gen) # 产出"糖果3"
next(candy_gen) # StopIteration!没货了
2.2 yield vs return 的区别
| 对比项 | yield | return |
|---|---|---|
| 作用 | 暂停函数,保留状态 | 结束函数,返回值 |
| 调用次数 | 可多次调用 | 只能调用一次 |
| 内存占用 | 按需生成,极小 | 一次性返回,可能很大 |
| 后续代码 | yield之后的代码下次继续执行 | return之后的代码不会执行 |
2.3 StopIteration:不是错误,是信号
很多人误以为 StopIteration是一个错误,其实它是迭代器用来喊"我下班了"的标准口号。
next()听到这个口号会报错(除非你处理了)for循环听到这个口号会乖乖收工,不会把异常展示给你看
# 手动处理 StopIteration
gen = make_candies()
while True:
try:
print(next(gen))
except StopIteration:
break # 正常结束,不是错误!
2.4 生成器表达式
除了用 yield定义生成器函数,还可以用生成器表达式:
# 列表推导(贪婪,占内存)
squares_list = [x**2 for x in range(1000000)]
# 生成器表达式(懒惰,省内存)
squares_gen = (x**2 for x in range(1000000))
记忆口诀:“圆括号是懒人,方括号是囤货王”
2.5 实用场景
- 读取大文件:逐行读取,不加载整个文件到内存
- 无限序列:斐波那契数列、质数生成
- 数据管道:A模块产数据 → B模块处理 → C模块输出
三、深浅拷贝:对象的独立程度
3.1 赋值、浅拷贝、深拷贝的区别
import copy
a = [1, {'age': 10}]
b = a # 赋值:共享同一个对象
c = a[:] # 浅拷贝:新列表,但内部元素共享引用
d = copy.deepcopy(a) # 深拷贝:完全独立,互不影响
a[1]['age'] = 12
print(b) # [1, {'age': 12}] —— 变了
print(c) # [1, {'age': 12}] —— 变了(字典共享)
print(d) # [1, {'age': 10}] —— 没变
3.2 情景化理解
想象你和朋友合租:
- 赋值:你们共用一个冰箱,你放进去的东西朋友也能看到
- 浅拷贝:你买了个新冰箱,但冰箱里的牛奶还是同一瓶(共享可变对象)
- 深拷贝:你搬了新家,有了完全独立的冰箱和所有东西
3.3 什么时候用深拷贝
当你需要:
- 修改嵌套的可变对象(字典、列表等)
- 不影响原始数据
- 保护数据完整性
四、猴子补丁:飞行中换引擎
4.1 什么是猴子补丁
猴子补丁(Monkey Patching)指的是在程序运行时,动态修改已有的类、函数或模块的行为,而无需修改其原始源代码。
class Calculator:
def add(self, a, b):
return a + b
# 猴子补丁:把加法改成乘法
def new_add(self, a, b):
return a * b
Calculator.add = new_add
calc = Calculator()
calc.add(2, 3) # 输出 6,而不是 5
4.2 为什么要用它
- 紧急修复第三方库的 Bug:官方还没发布新版本,临时"打补丁"
- 测试与 Mock:隔离测试环境,替换耗时操作
- 功能扩展:给无法修改源码的类动态添加功能
4.3 风险与最佳实践
主要风险:破坏代码的可维护性和可预测性。别人读代码时根本不知道这个函数已经被"调包"了。
最佳实践:
- 能用继承/装饰器解决的,尽量不用猴子补丁
- 如果非要用,一定要写清晰的注释
- 问题解决后及时清理掉
记忆比喻:猴子补丁就像创可贴——救急用的,别把全身都缠满胶带。
五、循环引用与垃圾回收
5.1 什么是循环引用
class MyObject:
pass
obj1 = MyObject()
obj2 = MyObject()
obj1.other = obj2 # obj1 引用 obj2
obj2.other = obj1 # obj2 引用 obj1 —— 形成循环!
.other是自定义的实例属性,就像给对象起了个名字叫"other",指向另一个对象。互相赋值就形成了循环引用。
5.2 为什么循环引用会导致内存泄漏
Python 的引用计数机制无法处理循环引用:
obj1的引用计数 = 1(被obj2.other引用)obj2的引用计数 = 1(被obj1.other引用)- 永远不会归零,内存不会被立即释放
5.3 Python 的双轨制回收策略
| 机制 | 原理 | 特点 | 能否处理循环引用 |
|---|---|---|---|
| 引用计数 | 对象被引用时计数+1,引用失效时-1,计数为0时立即回收 | 实时性强,速度快 | ❌ 不能 |
| 分代 GC | 定期扫描,标记-清除不可达对象 | 处理循环引用,有延迟 | ✅ 能 |
经验之谈:Python 主要靠引用计数"即时清理",靠 GC "定期打扫"循环引用的垃圾。
六、可调用对象:让实例像函数一样工作
6.1 什么是可调用对象
在 Python 中,能使用 ()进行调用的对象就是可调用对象:
- 函数(function)
- 类(class)—— 调用类会创建实例
- 实例对象 —— 如果该类定义了
__call__方法
6.2 通过 __call__让实例可调用
class Num:
def __init__(self):
self.num = 0
def __call__(self):
return self.num
n = Num()
n() # 调用 __call__,返回 0 —— 实例变成了可调用对象!
记忆技巧:
__call__就像给对象装了个"启动按钮",按一下就能执行。
七、collections 模块:数据容器增强包
7.1 核心类速查
| 类名 | 核心功能 | 应用场景 |
|---|---|---|
| defaultdict | 访问不存在的键时自动初始化 | 统计词频、分组聚合 |
| Counter | 统计元素出现次数 + 集合运算 | 词频统计、投票计票 |
| OrderedDict | 记录插入顺序(3.7+ dict 默认有序) | 需要明确"有序"语义的场景 |
| namedtuple | 创建轻量级、不可变对象 | 替代简单类、返回多个值 |
| deque | 双端队列 | 队列、栈、滑动窗口 |
7.2 代码示例
from collections import defaultdict, Counter, namedtuple
# defaultdict —— 自动初始化
d = defaultdict(int)
d['a'] += 1 # 即使'a'不存在,也自动创建并初始化为0
# Counter —— 计数神器
c = Counter(['a', 'b', 'a', 'c'])
c['a'] # 2
# namedtuple —— 命名元组
Point = namedtuple('Point', ['x', 'y'])
p = Point(1, 2)
p.x # 1
7.3 情景化记忆
- defaultdict:健忘症患者的备忘录,自动帮你记下来
- Counter:超市收银机,不仅能数,还能做集合运算
- namedtuple:身份证,发出去就不能改,但每个人有清晰的名字字段
八、元类:类的工厂
8.1 什么是元类
元类(metaclass)是创建类的类。在 Python 中,type是所有类的元类。
class Meta(type):
def __new__(cls, name, bases, dct):
dct['version'] = 1.0 # 动态添加类属性
return super().__new__(cls, name, bases, dct)
class Base(metaclass=Meta):
pass
Base.version # 1.0 —— 元类在创建类时注入的!
8.2 super()在元类中的作用
super().__new__()在元类中实际调用的是 type.__new__(),负责真正创建类对象。
你可以把这个过程想成一条流水线:
Meta.__new__先改图纸(修改类字典)super().__new__()把图纸送到type(总装厂)type按图纸造出类对象
记忆比喻:
type是总装厂,Meta是改装车间,super()是总装厂的联络员。
8.3 子类继承元类
子类会自动继承父类的元类:
class Child(Base): # 继承了 Base 的元类 Meta
pass
Child.version # 1.0 —— 同样被注入了 version 属性!
九、文件操作与 open() 模式
9.1 常用文件模式
| 模式 | 含义 | 说明 |
|---|---|---|
| r | 只读 | 默认模式 |
| w | 写入 | 会清空原文件 |
| a | 追加 | 在文件末尾添加 |
| b | 二进制 | 与其他模式组合使用 |
| + | 读写 | 与其他模式组合使用 |
9.2 模式组合示例
open('file.txt', 'r') # 文本只读
open('file.txt', 'rb') # 二进制只读
open('file.txt', 'w') # 文本写入(清空)
open('file.txt', 'wb') # 二进制写入(清空)
open('file.txt', 'a') # 文本追加
open('file.txt', 'ab') # 二进制追加(正确答案!)
open('file.txt', 'r+') # 文本读写
记忆口诀:r读 w写 a追加,b是二进制 t是文本。
十、asyncio:异步编程入门
10.1 asyncio.sleep(0)的奥秘
很多人以为 sleep(0)就是"不休息",但在异步编程中,它的核心目的恰恰是释放 CPU 控制权。
import asyncio
async def long_task():
for i in range(1000000):
do_something(i)
if i % 1000 == 0:
await asyncio.sleep(0) # 让出控制权,让其他协程有机会运行
原理:
sleep(0)向事件循环注册了一个"下一轮迭代时立刻响"的闹钟- 当前协程暂停,事件循环去执行其他就绪的协程
- 没有其他任务时,再回来继续执行
记忆比喻:
sleep(1)是"我排会儿队,等1分钟再来";sleep(0)是"我不排队了,你先让后面的人办"。
10.2 异步编程的核心概念
- 协程(Coroutine):可以暂停和恢复的函数
- 事件循环(Event Loop):协程的调度器
- await:暂停当前协程,等待另一个协程完成
- 非阻塞:不会让整个程序卡住
十一、math 模块:数学计算利器
11.1 常用常量和函数
| 常量/函数 | 数学含义 | 示例 |
|---|---|---|
| math.pi | 圆周率 π | math.pi ≈ 3.14159 |
| math.e | 自然常数 e | math.e ≈ 2.71828 |
| math.exp(x) | 指数函数 e^x | math.exp(2) ≈ 7.389 |
| math.log(x) | 自然对数 ln(x) | math.log(math.e) = 1 |
| math.pow(x, y) | x 的 y 次方 | math.pow(2, 3) = 8 |
11.2 math.exp() vs math.pow()
- math.exp(x):专门计算 e^x,精度更高,速度更快
- math.pow(x, y):计算任意底数的幂
最佳实践:计算 e 的幂时用
math.exp(),其他幂运算用math.pow()。
十二、函数参数进阶
12.1 * 在参数列表中的作用
*在参数列表中表示:后面的参数必须使用关键字参数传递。
def fun(a, *, b):
print(b)
fun(1, b=2) # ✅ 正确,b用关键字参数
fun(1, 2) # ❌ 错误,b必须用关键字参数
fun(1, 2, 3, 4) # ❌ 错误,参数不匹配
12.2 字典的 get() 方法
info = {'name': '班长', 'id': 100}
info.get('age') # None(没有默认值)
info.get('age', 18) # 18(有默认值)
结语
Python 的进阶知识就像一张网,各个知识点之间互相联系。理解这些概念,不仅能让你写出更高效、更优雅的代码,也是面试中展示技术深度的重要素材。
学习的过程就像拼图——每个知识点都是一块碎片,当你把它们拼在一起时,就能看到完整的画面。保持好奇心,多写代码,多踩坑,你会越来越接近这门语言的本质。
最后提醒:这篇笔记适合反复阅读和实践。每次读都会有新的理解,因为 Python 的深度远超表面。
更多推荐
所有评论(0)