闭包(Closure)
闭包是一种非常重要的 Python 高阶函数特性,它能让内部函数“记住”外部函数的变量,即使外部函数已经执行完毕。
一句话总结:
闭包 = 函数嵌套 + 内部函数使用外部函数变量 + 外部函数返回内部函数。
闭包的核心能力是——
保存外部函数的变量,不会随着外部函数结束而销毁。
闭包的基本定义
闭包满足三个条件:
- 必须有函数嵌套(即函数内部再定义函数)
- 内部函数使用了外部函数的变量
- 外部函数返回内部函数
示例:
# 外部函数
def func_out(num1):
# 内部函数
def func_inner(num2):
num = num1 + num2 # 使用了外部变量 num1
print(f"num 的值为:{num}")
return func_inner # 返回内部函数形成闭包
# 创建闭包实例
f = func_out(10)
f(6) # 打印:num 的值为:16
说明:
- func_out 执行结束后,num1 按理应销毁
- 但闭包保留了这个变量的值
- 内部函数 func_inner 可以继续访问 num1
闭包中修改外部变量(nonlocal)
当内部函数想修改外部函数的变量时,必须使用 nonlocal 声明:
def func_out(num1):
def func_inner(num2):
nonlocal num1 # 声明使用外部函数变量
num1 += num2
print(f"num1 的值为:{num1}")
return func_inner
f = func_out(10)
f(8) # 打印:num1 的值为:18
nonlocal 的作用:
- 表示 “这个变量不是本地变量,请向外层函数查找并绑定它”
- 否则 Python 会认为 num1 是 func_inner 内的新变量,导致报错
闭包的优点与缺点
优点
- 无需定义全局变量即可保存数据状态(例如计数器、缓存等场景)
- 变量被封装在外部函数作用域内,更安全,不易被随意修改
- 代码结构更清晰,易读性强
缺点
- 闭包保存的外部变量 不会销毁
→ 有可能带来额外的 内存占用
小结
闭包的本质作用是:
让外部函数的变量在函数结束后依然存活,并由内部函数继续使用或修改。
它是 Python 函数式编程的重要基础,也为装饰器机制提供了前提条件。
装饰器(Decorator)
装饰器是 Python 中最强大的语法特性之一,它的作用是:
在不修改原函数代码的前提下,为其增加新的功能。
装饰器本质上依赖闭包(内部函数持有外部变量)。
它让代码变得更优雅、更灵活,尤其适用于:
- 记录日志
- 权限校验
- 性能统计
- 事务管理
- 缓存机制
- 统一接口格式
为什么需要装饰器?(动机)
假设有一个函数:
def func():
print("这是一个函数")
现在想给它增加一个功能:执行前输出“开始执行”。
传统做法是:
def func():
print("开始执行")
print("这是一个函数")
但这会修改原函数代码,且如果很多函数都需要增加相同逻辑,会产生大量重复代码。
装饰器能完美解决该问题。
装饰器的基本结构
装饰器依赖闭包机制:
def decorator(func): # 接收被装饰的函数
def wrapper(*args, **kwargs):
print("开始执行")
result = func(*args, **kwargs) # 调用原函数
print("结束执行")
return result
return wrapper
使用装饰器:
@decorator
def func():
print("这是一个函数")
func()
输出:
开始执行
这是一个函数
结束执行
说明:
- @decorator 等价于:
func = decorator(func) - wrapper 是对原函数的“增强版本”
- 不修改 func 本体,却达成增强效果
装饰器语法糖(@ 语法)
@decorator
def test():
...
等价于:
def test():
...
test = decorator(test)
@ 是增强函数最优雅的写法。
带参数的装饰器
有时希望装饰器可以“配置参数”,例如:
- 配置日志等级
- 设置权限等级
- 设置统计模式
此时需要再多包一层函数:
def outer(flag):
def decorator(func):
def wrapper(*args, **kwargs):
print("装饰器参数:", flag)
return func(*args, **kwargs)
return wrapper
return decorator
使用:
@outer("DEBUG")
def func():
print("函数执行")
func()
输出:
装饰器参数: DEBUG
函数执行
原理:
- outer(flag) 返回 decorator
- decorator(func) 返回 wrapper
装饰器叠加三层函数 → 即可支持参数。
装饰器处理返回值
wrapper 必须返回原函数的返回值:
def decorator(func):
def wrapper(*args, **kwargs):
print("开始执行")
result = func(*args, **kwargs)
print("结束执行")
return result
return wrapper
多个装饰器叠加执行顺序
示例:
def deco1(func):
def wrapper():
print("deco1 start")
func()
print("deco1 end")
return wrapper
def deco2(func):
def wrapper():
print("deco2 start")
func()
print("deco2 end")
return wrapper
@deco1
@deco2
def func():
print("函数执行")
执行:
func()
结果:
deco1 start
deco2 start
函数执行
deco2 end
deco1 end
规律:
- 离函数最近的装饰器先执行
- 装饰器从下往上包裹(类似洋葱结构)
保留原函数信息:functools.wraps
被装饰的函数会丢失原函数信息:
func.__name__ → wrapper
解决方法:
from functools import wraps
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
这样就能保持函数名、注释等元信息。
类装饰器(使用 call)
装饰器不仅可以是函数,还可以是类,只需实现 __call__ 方法:
class MyDecorator:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print("类装饰器开始")
result = self.func(*args, **kwargs)
print("类装饰器结束")
return result
使用:
@MyDecorator
def func():
print("函数执行")
func()
输出:
类装饰器开始
函数执行
类装饰器结束
类装饰器的优点:
- 更适合保存状态(如计数器、缓存)
- 可扩展性强
装饰器的使用场景
装饰器是真正的工程化工具,常用于:
| 场景 | 示例 |
|---|---|
| 日志记录 | @log_decorator |
| 权限校验 | @auth_required |
| 缓存 | @lru_cache |
| 性能监控 | @timeit |
| 事务控制 | @transactional |
| API 规范 | @response_format |
装饰器让横切逻辑(Cross-Cutting Logic)更加优雅。
本章总结
| 内容 | 描述 |
|---|---|
| 装饰器本质 | 闭包函数增强原函数 |
| 语法糖 | @decorator |
| 带参数装饰器 | 三层函数结构 |
| 多装饰器 | 从下往上包裹,从上往下执行 |
| 类装饰器 | 使用 call 增强函数 |
| wraps | 保留原函数元信息 |
一句话总结:
装饰器是一种无侵入式增强函数的技术,Python 中的“魔法工具”。
生成器(Generator)
生成器是 Python 中一种非常重要的特殊迭代器,它可以“边计算边返回数据”,不需要一次性把所有数据加载到内存中。
一句话总结:
生成器 = 带有
yield的函数,可以逐个返回数据,实现惰性求值。
生成器在处理大量数据、长序列、流式数据输出时非常高效。
为什么需要生成器?
普通函数:
- 一次性计算
- 一次性返回
- 占用大量内存
生成器:
- 需要时才生成数据(惰性求值)
- 不占用额外内存
- 支持无限序列输出
实际应用场景:
- 读取大文件
- 网络流式数据
- 无限序列(如斐波那契数列)
- 节省内存的大规模数据处理
生成器的基本语法(yield)
使用 yield 替代 return:
def func():
print("执行1")
yield 10
print("执行2")
yield 20
g = func()
使用:
print(next(g)) # 执行到第一个 yield
print(next(g)) # 执行到第二个 yield
运行结果:
执行1
10
执行2
20
说明:
- 每次
next()执行到下一个 yield - yield 返回值给调用者
- yield 会记住函数状态,下次继续执行
使用生成器遍历数据
def generator():
for i in range(3):
print("生成:", i)
yield i
for v in generator():
print("接收:", v)
输出:
生成:0
接收:0
生成:1
接收:1
生成:2
接收:2
生成器非常适合“逐行处理文件”“逐条处理数据”等场景。
生成器表达式(更简洁的写法)
列表推导式:
lst = [x * 2 for x in range(5)]
生成器表达式:
gen = (x * 2 for x in range(5))
len(lst) = 5,立即创建所有数据
len(gen) 无法计算 → 数据按需生成
获取数据:
for i in gen:
print(i)
send() 方法(向生成器发送数据)
生成器不仅可以产出数据,还可以接收数据。
示例:
def func():
x = yield "hello"
print("收到:", x)
g = func()
print(next(g)) # 启动生成器
g.send("Python") # 发送数据
输出:
hello
收到: Python
说明:
- 第一次必须用 next() 启动生成器
- send(value) 会把 value 作为 yield 表达式的返回值
send 的典型用途:
- 协程(早期协程机制)
- 外部动态控制生成器内部逻辑
生成器实现无限序列
例如无限斐波那契:
def fib():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
g = fib()
for _ in range(10):
print(next(g))
生成器天然适合这种“无限值序列”,不会占用额外内存。
生成器的优点
| 优点 | 描述 |
|---|---|
| 节省内存 | 不需要一次性创建全部数据 |
| 惰性求值 | 用到时才生成 |
| 易于表达无限序列 | 无限循环 + yield |
| 适用于大数据流处理 | 如大文件读取、网络流 |
本章总结
| 特性 | 说明 |
|---|---|
| yield | 让函数成为生成器 |
| next() | 获取下一个值 |
| send() | 向生成器发送数据 |
| 生成器表达式 | (x for x in …) |
| 惰性求值 | 节省内存的核心 |
一句话总结:
生成器让 Python 更优雅、更高效,是高阶技巧中必掌握的核心能力之一。
迭代器(Iterator)
迭代器是 Python 中支持“逐个取值”的特殊对象,它是 for 循环背后的核心执行机制。
一句话总结:
迭代器 = 能被 next() 调用、能逐个返回数据的对象。
Python 中所有可迭代对象(如 list、dict、str、tuple)内部都依赖迭代器机制。
可迭代对象(Iterable)与迭代器(Iterator)
必须区分两个概念:
| 类型 | 特点 | 示例 |
|---|---|---|
| Iterable(可迭代对象) | 能使用 for 循环遍历 | list、str、tuple、dict |
| Iterator(迭代器) | 能被 next() 调用,每次返回一个值 | 文件对象、生成器、iter() 创建的对象 |
判断对象是否为可迭代对象:
from collections.abc import Iterable
print(isinstance([1,2,3], Iterable)) # True
判断是否为迭代器:
from collections.abc import Iterator
print(isinstance(iter([1,2,3]), Iterator)) # True
iter() 将可迭代对象转换成迭代器
例如:
lst = [10, 20, 30]
it = iter(lst)
逐个取值:
print(next(it)) # 10
print(next(it)) # 20
print(next(it)) # 30
如果继续 next(it) 会抛出 StopIteration,表示“没有更多数据”。
for 循环就是不断执行 next(),直到 StopIteration。
为什么需要迭代器?
迭代器相比普通序列有以下优势:
节省内存
数据不需要一次性加载(例如文件逐行读取)。
惰性求值
只有在需要时才取下一个元素。
统一遍历接口
for 循环可以遍历各种容器,因为它们都遵循迭代器协议。
自定义迭代器
要让一个类成为迭代器,必须实现两个方法:
__iter__():返回迭代器对象本身__next__():返回下一个值,没有更多数据时抛出 StopIteration
示例:自定义迭代器,生成 1 到 n 的数字。
class MyIterator:
def __init__(self, n):
self.n = n
self.current = 1
def __iter__(self):
return me
def __next__(self):
if self.current <= self.n:
value = self.current
self.current += 1
return value
else:
raise StopIteration
使用:
for i in MyIterator(5):
print(i)
输出:
1
2
3
4
5
自定义可迭代对象(Iterable)
如果你想让一个对象可用于 for 遍历,需要:
- 实现
__iter__()方法 - 返回一个迭代器对象(如上面的 MyIterator)
示例:
class MyList:
def __init__(self, data):
self.data = data
def __iter__(self):
return iter(self.data) # 返回系统提供的迭代器
使用:
ml = MyList([1, 2, 3])
for x in ml:
print(x)
说明:
- MyList 本身不是迭代器,但它是可迭代对象
- for 循环内部会自动调用 iter(ml),从而获取迭代器
生成器就是一种迭代器
生成器(含 yield 的函数)天然拥有 iter 和 next 方法,是迭代器的一种。
验证:
def gen():
yield 1
yield 2
g = gen()
from collections.abc import Iterator
print(isinstance(g, Iterator)) # True
StopIteration 的作用
迭代器必须通过 StopIteration 来告诉循环:
“已经没有更多数据了。”
for 循环内部结构如下(概念化):
it = iter(obj)
while True:
try:
value = next(it)
# 使用 value
except StopIteration:
break
这就是 for 循环不需要索引的原因。
本章总结
| 概念 | 说明 |
|---|---|
| Iterable | 能被 for 遍历 |
| Iterator | 能执行 next() |
| iter() | 把可迭代对象变成迭代器 |
| next() | 获取下一个值 |
| StopIteration | 表示迭代结束 |
| 自定义迭代器 | 实现 iter 和 next |
| 生成器 | 天然迭代器 |
一句话总结:
迭代器是 Python 遍历机制的核心,for 的底层就是不断调用 next()。
Lambda 表达式与内置高阶函数
本章介绍 Python 中最常用的高阶函数与匿名函数技巧,包括:
- Lambda 匿名函数
- map
- filter
- sorted
- reduce
它们是编写简洁、高效 Python 代码的必备工具。
Lambda 匿名函数
Lambda 是一种 匿名函数,用于快速声明简单函数。
基本语法:
lambda 参数: 表达式
示例:
func = lambda x: x * 2
print(func(5)) # 10
等价于:
def func(x):
return x * 2
适用场景:
- 函数非常简单
- 临时使用一次
- 与高阶函数搭配(map / filter / sorted)
map(对每个元素执行函数)
语法:
map(函数, 可迭代对象)
示例:所有数字乘 2
nums = [1, 2, 3, 4]
result = map(lambda x: x * 2, nums)
print(list(result))
输出:
[2, 4, 6, 8]
作用:
map = 批量加工工具,将每个元素做变换
filter(过滤元素)
语法:
filter(函数, 可迭代对象)
函数返回 True 的元素会被保留下来。
示例:筛选偶数
nums = [1, 2, 3, 4]
result = filter(lambda x: x % 2 == 0, nums)
print(list(result))
输出:
[2, 4]
作用:
filter = 批量筛选工具
sorted(排序,支持 key 规则)
基本排序:
sorted([3, 1, 2]) # [1, 2, 3]
按 key 排序(更常用):
students = [
{"name": "Tom", "age": 19},
{"name": "Lucy", "age": 17},
{"name": "Jack", "age": 20},
]
result = sorted(students, key=lambda x: x["age"])
输出:
[{'name': 'Lucy', 'age': 17}, {'name': 'Tom', 'age': 19}, {'name': 'Jack', 'age': 20}]
倒序:
sorted(students, key=lambda x: x["age"], reverse=True)
作用:
sorted + key = 自定义排序规则
reduce(连续处理数据)
reduce 用于对序列进行“累积处理”。
需要导入:
from functools import reduce
示例:所有数相加
nums = [1, 2, 3, 4]
result = reduce(lambda x, y: x + y, nums)
print(result)
输出:
10
reduce 的执行过程:
(((1 + 2) + 3) + 4)
常用于:
- 求和
- 连乘
- 合并结构
- 递归处理逻辑
高阶函数综合示例
任务:筛选出大于 10 的数,然后平方,并按结果排序。
nums = [3, 15, 2, 20, 9]
# 1. 过滤
filtered = filter(lambda x: x > 10, nums)
# 2. 映射(平方)
mapped = map(lambda x: x * x, filtered)
# 3. 排序
result = sorted(mapped)
print(result)
输出:
[225, 400]
一条语句也能写:
result = sorted(map(lambda x: x * x, filter(lambda x: x > 10, nums)))
这就是函数式写法的威力。
本章小结
| 技术 | 作用 |
|---|---|
| lambda | 快速定义简单函数 |
| map | 批量加工元素 |
| filter | 批量筛选元素 |
| sorted | 自定义排序规则 |
| reduce | 连续运算、累积处理 |
一句话总结:
Lambda + 高阶函数 = Python 中编写简洁优美代码的高级技巧。
推导式(Comprehensions)
推导式是 Python 中用于构建列表、字典、集合的简洁写法,具有:
- 更少的代码量
- 更高的可读性
- 更快的执行效率
一句话总结:
推导式 = 用一个表达式构造一个新容器
它是 Python 高阶技巧中最常用也最实用的语法糖。
列表推导式(List Comprehension)
基本结构:
[表达式 for 变量 in 可迭代对象]
示例:
lst = [x * 2 for x in range(5)]
print(lst)
输出:
[0, 2, 4, 6, 8]
带条件筛选
lst = [x for x in range(10) if x % 2 == 0]
输出:
[0, 2, 4, 6, 8]
复杂表达式示例
names = ["Tom", "Jack", "Linda"]
result = [name.upper() for name in names]
输出:
['TOM', 'JACK', 'LINDA']
字典推导式(Dict Comprehension)
基本结构:
{key_expr: value_expr for 变量 in 可迭代对象}
示例:
nums = [1, 2, 3]
d = {x: x * x for x in nums}
print(d)
输出:
{1: 1, 2: 4, 3: 9}
带条件的字典推导式
d = {x: x * 2 for x in range(6) if x % 2 == 0}
输出:
{0: 0, 2: 4, 4: 8}
字典反转(常用)
origin = {"Tom": 18, "Jack": 20}
reversed_dict = {v: k for k, v in origin.items()}
输出:
{18: 'Tom', 20: 'Jack'}
集合推导式(Set Comprehension)
基本结构:
{表达式 for 变量 in 可迭代对象}
示例:
s = {x * 2 for x in range(5)}
print(s)
输出(集合无序):
{0, 2, 4, 6, 8}
去重场景(非常常用)
例如对一组数据取平方并去重:
s = {x * x for x in [1, 2, 2, 3, 3]}
print(s)
输出:
{1, 4, 9}
嵌套推导式(高级使用)
生成九九乘法表:
table = [
f"{i}*{j}={i*j}"
for i in range(1, 10)
for j in range(1, i + 1)
]
输出示例:
['1*1=1', '2*1=2', '2*2=4', ...]
或生成二维列表:
matrix = [[i + j for j in range(3)] for i in range(3)]
输出:
[[0, 1, 2], [1, 2, 3], [2, 3, 4]]
推导式与函数式编程对比
例如:筛选偶数并平方
使用 map + filter:
result = list(map(lambda x: x * x, filter(lambda x: x % 2 == 0, range(10))))
使用推导式(更直观):
result = [x * x for x in range(10) if x % 2 == 0]
→ 推导式更加简洁、可读性更强。
推导式的优势
| 优势 | 说明 |
|---|---|
| 简洁 | 一行解决多行逻辑 |
| 高效 | 比普通 for 循环速度更快 |
| 可读性强 | 结构统一、逻辑清晰 |
| 灵活 | 支持筛选、表达式、嵌套等 |
本章小结
| 推导式类型 | 示例 |
|---|---|
| 列表推导式 | [x*2 for x in data] |
| 字典推导式 | {k:v for k,v in items} |
| 集合推导式 | {x for x in data} |
| 带条件推导式 | [x for x in data if x>0] |
| 嵌套推导式 | [[... for j] for i] |
一句话总结:
推导式是 Python 编程优雅性的象征,让复杂处理逻辑变得异常简洁。
异常高级技巧
基础异常处理你已经很熟悉:try / except / finally。
本章将介绍更专业、更工程化的异常技巧,包括:
- 多异常捕获
- 捕获所有异常
- 自定义异常
- 异常链(raise from)
- 异常安全代码结构
- 使用异常控制业务流程
这些技巧在真实项目中非常实用。
多异常捕获
当你需要同时处理多种不同类型的异常时,可以写多个 except:
try:
num = int(input("输入数字:"))
print(10 / num)
except ValueError:
print("输入必须是数字")
except ZeroDivisionError:
print("不能除以 0")
适用于不同异常需要不同处理逻辑的情况。
合并捕获多个异常
如果多个异常处理逻辑一致:
try:
num = int("abc")
except (ValueError, TypeError):
print("数据格式错误")
用括号统一捕获,有助于代码简化。
捕获所有异常(Exception)
try:
...
except Exception as e:
print("错误信息:", e)
适用于:
- 复杂逻辑
- 无法预估具体异常类型
- 提升系统稳定性
但 不建议滥用,否则真实错误会被掩盖。
自定义异常
用于业务逻辑中出现“非系统报错,但需要使用异常机制表示错误”的情况。
定义异常需要继承 Exception:
class PasswordTooShortError(Exception):
pass
使用:
def set_password(pwd):
if len(pwd) < 6:
raise PasswordTooShortError("密码长度不能少于 6 位")
捕获:
try:
set_password("123")
except PasswordTooShortError as e:
print("密码错误:", e)
自定义异常非常适合:
- 业务错误提示
- 表示特殊流程跳转
- 框架设计(如 Django、Flask)
异常链(raise from)
当你想在捕获一个异常后,抛出另一个新的异常,同时保留原始异常信息:
try:
int("abc")
except ValueError as e:
raise RuntimeError("转换失败") from e
输出会显示:
During handling of the above exception, another exception occurred:
→ 非常有利于调试!
finally 的作用(无论如何都会执行)
finally 常用于:
- 关闭文件、数据库连接
- 释放资源
- 打印日志
示例:
try:
f = open("a.txt")
data = f.read()
except Exception:
print("文件读取失败")
finally:
f.close()
即使发生异常,也会执行 close()。
else:没有异常时执行
很多人不知道 else 是 try-except 的一部分:
try:
num = int("123")
except:
print("出错")
else:
print("成功执行,没有异常")
适用于:
- 想区分“正常逻辑”和“异常处理”
- 让代码结构更清晰
使用异常进行业务控制(高阶用法)
示例:检测年龄是否合法
class AgeError(Exception):
pass
def set_age(age):
if age < 0:
raise AgeError("年龄不能为负数")
print("年龄设置成功")
说明:
- 比 if-else 更强的流程控制
- 错误直接中断当前流程
- 在大型业务系统中常见
记录异常日志(企业级写法)
import traceback
try:
1 / 0
except Exception as e:
with open("error.log", "a") as f:
f.write(traceback.format_exc())
优势:
- 保留完整错误堆栈
- 易于排查线上问题
本章总结
| 技巧 | 说明 |
|---|---|
| 多异常捕获 | 为不同错误设计不同逻辑 |
| Exception 捕获 | 捕获所有异常 |
| 自定义异常 | 用于业务逻辑错误处理 |
| raise from | 构建异常链,利于调试 |
| finally | 无论是否异常都执行 |
| else | 仅在无异常时执行 |
| 异常日志记录 | 保存错误堆栈,定位问题 |
一句话总结:
高级异常处理的目标不是“捕获更多异常”,而是让系统更稳定、可控、易调试。
上下文管理器(with 语句)
上下文管理器是 Python 中用于自动管理资源的机制,最常见的例子就是:
with open("a.txt") as f:
...
with 的核心优势:
- 自动执行“进入 → 离开”逻辑
- 自动关闭文件、连接等资源
- 自动处理异常场景确保资源释放
一句话总结:
上下文管理器 = 自动执行善后工作的机制。
为什么需要上下文管理器?
不使用 with 时,你必须手动关闭资源:
f = open("a.txt", "r")
try:
data = f.read()
finally:
f.close()
使用 with:
with open("a.txt", "r") as f:
data = f.read()
自动关闭资源,更优雅、更安全。
上下文管理器的原理:enter 与 exit
任意对象只要实现了下面两个方法,就能成为一个上下文管理器:
__enter__(self)
__exit__(self, exc_type, exc_val, exc_tb)
流程如下:
- 执行 with 时,自动调用
__enter__() - 执行代码块
- 无论是否产生异常,最终都会调用
__exit__()
自定义上下文管理器(类方式)
示例:自定义一个能自动打印执行流程的上下文管理器。
class MyContext:
def __enter__(self):
print("进入上下文")
return "返回给 as 的值"
def __exit__(self, exc_type, exc_val, exc_tb):
print("退出上下文")
if exc_type:
print("发生异常:", exc_val)
return True # 防止异常继续抛出
使用:
with MyContext() as msg:
print(msg)
输出:
进入上下文
返回给 as 的值
退出上下文
上下文管理器处理异常
如果 with 内部代码报错:
with MyContext():
raise ValueError("测试异常")
exit 的三个参数:
- exc_type:异常类型
- exc_val:异常对象
- exc_tb:异常的 traceback
如果 exit 返回:
- True:异常会被吞掉(不继续抛出)
- False:异常会继续抛出
使用 contextlib.contextmanager(装饰器写法)
相比类写法,函数写法更轻量。
依赖装饰器:
from contextlib import contextmanager
示例:
@contextmanager
def my_context():
print("进入上下文")
yield "hello"
print("退出上下文")
使用:
with my_context() as msg:
print(msg)
输出:
进入上下文
hello
退出上下文
说明:
yield前的代码类似 enteryield后的代码类似 exit- 写法更简洁优雅
应用场景
上下文管理器常用于管理需要释放的资源:
| 场景 | 示例 |
|---|---|
| 文件操作 | open() |
| 数据库连接 | with connection.cursor(): |
| 线程锁 | with lock: |
| 网络连接 | 自动关闭 socket |
| 事务控制 | 自动提交 / 回滚 |
| 临时目录、测试环境 | with TemporaryDirectory() |
在大型 Python 项目中,with 是代码质量的重要标志。
本章总结
| 技巧 | 说明 |
|---|---|
| with 语句 | 自动管理资源 |
| enter | 上下文进入逻辑 |
| exit | 上下文退出逻辑(含异常处理) |
| contextmanager | 使用装饰器定义上下文管理器 |
| 应用场景 | 文件、数据库、锁、事务等 |
一句话总结:
上下文管理器让资源使用更安全,让代码更优雅,是 Python 工程化必备技巧。
模块导入高级技巧
Python 的模块导入系统非常灵活,本章将讲解:
__all__导出控制- 绝对导入与相对导入
- 动态导入(importlib)
- 模块导入本质
- import 的执行机制
这些技巧在实际开发、模块化设计、项目重构中非常重要。
使用
__all__控制模块可导出内容当你使用:
from module import *时,Python 会根据模块内的
__all__列表决定能导出的内容。示例:
__all__ = ["func1", "Student"] def func1(): print("函数1") def func2(): print("函数2") class Student: pass当执行:
from module import *可用:
- func1
- Student
不可用:
- func2(因为不在 all 中)
作用:
- 明确模块对外暴露的接口
- 保护内部函数不被直接使用
- 让模块的 API 更清晰规范
绝对导入与相对导入
绝对导入(推荐)
基于项目根目录的导入方式:
from package.module import func优点:
- 清晰
- 不易出错
- 适合大型项目
相对导入
使用 . 和 .. 表示当前及上级目录:
from .module_a import func from ..common.utils import helper相对导入只能在包内使用。
标记含义:
写法 含义 .当前目录 ..上一级目录 ...上上级目录 适用场景:
- 包内部模块结构稳定
- 需要避免重复书写长路径
动态导入(importlib)
在某些场景,如插件系统、按需加载模块时,需要动态导入模块。
import importlib module = importlib.import_module("math") print(module.sqrt(9))输出:
3.0还能按字符串导入自定义模块:
m = importlib.import_module("mypackage.mymodule")常用于:
- 插件系统
- 动态配置加载
- 根据字符串执行特定模块
模块只会被导入一次(import 缓存机制)
即使写了多次 import,相同模块只会执行一次。
示例:
import mymodule import mymodulemymodule.py 里的顶级代码只会执行一次。
原因:
- Python 会将导入过的模块缓存到
sys.modules- 再次导入时会直接读取缓存
查看缓存:
import sys print(sys.modules)
import 执行流程(理解模块导入本质)
当你执行:
import modulePython 实际做了三件事:
- 在 sys.path 中查找 module.py 或包目录
- 加载模块内容并执行其顶级代码
- 将模块对象放入 sys.modules 缓存
因此:
- 任何写在模块最外层的代码都会执行一次
- 函数与类只是被创建,不会自动执行
循环导入问题(Import Cycle)
例如:
- a.py 导入 b.py
- b.py 又导入 a.py
若存在互相依赖,就会导致:
ImportError: cannot import name ...解决方案:
- 将公共逻辑抽离到独立模块
- 使用局部导入(在函数内部 import)
- 使用动态导入(importlib.import_module)
避免循环导入是模块化设计的关键。
局部导入(函数内部 import)
在函数内部导入模块:
def func(): import math print(math.sqrt(4))用途:
- 避免循环依赖
- 减少启动时间(按需加载)
- 降低全局命名污染
缺点:
- 多次调用时会重复查找,但缓存机制仍能减少开销
本章总结
技巧 作用 __all__控制对外暴露的接口 相对导入 包内部模块组织 动态导入 插件机制、按需加载 import 缓存 模块只执行一次 局部导入 避免循环依赖 sys.path 决定模块的查找顺序 一句话总结:
模块导入是 Python 工程化的核心能力,高级技巧让你的项目更规范、更灵活、更易维护。
Python 内存管理与变量机制
本章涵盖 Python 中最核心的底层机制之一,包括:
- 引用计数
- 垃圾回收机制
- 可变与不可变对象
- 深拷贝与浅拷贝
- 变量名与对象的关系(非常关键)
理解这些知识能让你更清楚 Python 程序的运行方式,有助于避免内存泄漏、变量污染、意外修改等问题。
变量名不是“盒子”,而是“标签”
Python 中的变量本质上是对象的引用(指针),不是传统语言中的存储空间。
示例:
a = 10 b = a含义不是“把 a 的值复制给 b”,而是:
a → 指向 10 b → 指向 10a 和 b 都指向同一个对象。
如果你再执行:
a = 20此时:
a → 指向 20(新的对象) b → 仍然指向 10变量只是名字,真正的数据存储在对象中。
引用计数(Reference Counting)
Python 最主要的垃圾回收机制是引用计数:
每个对象都有一个引用计数器,当计数为 0 时,该对象会立即被销毁。
示例:
a = [1, 2, 3] b = a c = a引用次数 = 3
若执行:del b del c引用次数 = 1
再执行:del a引用次数 = 0 → 内存释放。
你可以使用 sys.getrefcount 查看引用计数:
import sys x = 100 print(sys.getrefcount(x))
可变对象与不可变对象
Python 中的对象可分为:
不可变对象(修改会创建新对象)
- int
- float
- str
- tuple
- bool
示例:
s = "abc" s2 = s s = "abcd"“abcd” 是新对象,与原字符串 “abc” 无关。
可变对象(修改不会创建新对象)
- list
- dict
- set
示例:
lst = [1, 2, 3] lst2 = lst lst.append(4)结果:
lst → [1, 2, 3, 4] lst2 → [1, 2, 3, 4]原因:lst 与 lst2 指向同一个列表对象。
浅拷贝与深拷贝
浅拷贝(copy)
只复制最外层对象,内部引用保持不变。
import copy lst = [[1, 2], [3, 4]] lst2 = copy.copy(lst) lst2[0][0] = 999 print(lst) # [[999, 2], [3, 4]]→ 内部列表被共用。
深拷贝(deepcopy)
完全复制整个结构,内部对象也会复制。
lst3 = copy.deepcopy(lst) lst3[0][0] = 111 print(lst) # 不受影响适用场景:
- 多层嵌套结构(如 JSON)
- 复杂对象传递时避免互相影响
垃圾回收(GC)机制
Python 采用:
- 引用计数(主机制)
- 循环垃圾检测(GC)
- 分代回收
循环引用示例:
a = [] b = [] a.append(b) b.append(a)此时引用计数永远 > 0,但对象失去了实际用途。
→ Python 的循环垃圾回收器会自动回收这些“不可达对象”。
变量作用域(LEGB 原则)
LEGB 是 Python 查找变量名时的顺序:
L:Local 局部作用域 E:Enclosing 外层函数作用域 G:Global 全局 B:Built-in 内置作用域示例:
x = 1 def outer(): x = 2 def inner(): x = 3 print(x) # 输出 3 inner() outer()Python 会从内到外一层一层查找变量。
global 与 nonlocal
global:修改全局变量
count = 0 def add(): global count count += 1global 作用:绑定外部(全局)变量。
nonlocal:修改外层函数变量(闭包)
def outer(): x = 10 def inner(): nonlocal x x += 1 inner() print(x)输出:
11nonlocal 用于闭包,是闭包修改外部变量的唯一方式。
本章总结
机制 说明 引用计数 对象计数归零 → 自动销毁 垃圾回收 自动检测循环引用并清理 可变/不可变对象 影响变量修改是否影响其他引用 浅拷贝 外层复制,内层共享 深拷贝 完全复制,不共享 global 修改全局变量 nonlocal 修改外层函数变量 变量名本质 变量名 = 对象引用(标签) 一句话总结:
理解 Python 内存机制,可以避免意外数据共享、内存泄漏、变量污染等常见错误,是掌握 Python 高阶写法的关键。
元编程(Metaprogramming)
元编程指的是:代码可以操作代码本身。
Python 作为动态语言,提供丰富的元编程能力,包括:
eval()exec()- 反射(
getattr、setattr、hasattr)- 动态创建变量
- 动态执行函数
- 动态导入模块
元编程让 Python 在动态场景下具有极强的灵活性,例如:
- 根据字符串名字执行函数
- 动态调用对象属性
- 动态生成代码
- 构建插件式系统
eval:执行字符串表达式并返回结果
作用:
把字符串当成 Python 表达式执行,并返回结果。
示例:
result = eval("1 + 2 + 3") print(result)输出:
6eval 可执行任意表达式:
eval("len([1, 2, 3])") # 3⚠ 安全警告:
eval 会执行任意代码,不要在用户可控输入中使用。
exec:执行任意 Python 代码(不返回值)
exec 用于执行多行代码或复杂逻辑。
code = """ for i in range(3): print(i) """ exec(code)输出:
0 1 2区别:
函数 作用 eval 执行表达式并返回结果(单行) exec 执行代码块,不返回结果(多行)
反射(getattr / setattr / hasattr)
反射是 Python 动态语言最强大的能力之一:
根据“字符串名字”操作对象的属性或方法。
示例类:
class Student: def __init__(self): self.name = "Tom" def speak(self): print("hello")
getattr:根据字符串获取属性或方法
stu = Student() attr = getattr(stu, "name") print(attr)输出:
Tom获取方法:
method = getattr(stu, "speak") method() # 调用方法
setattr:动态设置属性
setattr(stu, "age", 20) print(stu.age)输出:
20动态修改已有属性:
setattr(stu, "name", "Jack")
hasattr:判断属性是否存在
print(hasattr(stu, "name")) # True print(hasattr(stu, "age")) # False(如果之前没设置)反射常用于:
- 动态调用方法(插件系统)
- 框架开发(Django ORM、Flask 路由)
- 根据配置调用类方法
globals() / locals()(动态变量操作)
globals():操作全局变量字典
globals()["x"] = 100 print(x) # 100动态创建变量名。
locals():操作局部变量(只读)
在函数中使用:
def func(): print(locals()) func()
动态执行函数名(字符串 → 函数调用)
假设你有如下函数:
def add(a, b): return a + b想根据字符串执行:
func_name = "add" func = globals()[func_name] print(func(3, 5))输出:
8这在:
- 命令解析器
- API 调用分发
- 插件系统
中非常常用。
动态创建类实例
示例:
class Dog: pass class Cat: pass class_name = "Dog" cls = globals()[class_name] obj = cls() print(obj)可以根据字符串动态创建对象,非常灵活。
importlib:动态导入模块(插件系统核心)
import importlib module = importlib.import_module("math") print(module.sqrt(16))可根据配置文件动态加载模块,实现插件化架构。
元编程的典型使用场景
场景 示例 动态调用函数 getattr(obj, “func_name”)() 插件机制 importlib 动态导入模块 ORM 框架 根据字段动态构建 SQL 自动路由匹配 Flask 中根据 URL 字符串匹配函数 序列化 根据属性名自动生成 JSON 自动构建对象 配置驱动结构 元编程是 Python 在框架开发领域(Django、Flask、FastAPI)如此强大的核心原因。
本章总结
技术 说明 eval 执行表达式并返回结果 exec 执行代码块,无返回值 getattr 字符串 → 获取属性或方法 setattr 字符串 → 设置属性 hasattr 判断属性是否存在 globals 动态全局变量操作 importlib 动态导入模块 反射 动态语言的灵魂能力 一句话总结:
元编程让 Python 拥有操作自身结构的能力,是高级开发(插件系统、框架开发、自动化系统)不可或缺的一部分。
综合案例:装饰器 + 闭包 + 迭代器 + 反射的高级应用
本综合案例展示如何将本章节中学习的高阶技巧组合在一起,包括:
- 闭包
- 装饰器
- 迭代器
- 反射
- Lambda
- 上下文管理器思想(资源管理)
这是一个典型的“高级 Python 项目技巧整合案例”。
需求背景
我们要实现一个“带日志记录功能的任务执行器”,要求:
- 任务可以动态注册(使用反射)
- 每个任务执行前后自动打印日志(装饰器)
- 任务执行器应能逐个输出任务(迭代器)
- 可以为某些任务保存状态(闭包)
最终展示 Python 高阶语法组合使用的完整能力。
步骤拆解
下面将分步骤构建案例。
使用闭包保存任务执行次数
闭包让函数拥有“记忆”。
def task_counter(task_name): count = 0 def wrapper(): nonlocal count count += 1 print(f"任务 {task_name} 已执行 {count} 次") return wrapper示例:
count_task = task_counter("download") count_task() count_task()输出:
任务 download 已执行 1 次 任务 download 已执行 2 次
使用装饰器自动记录任务执行日志
def log_task(func): def wrapper(*args, **kwargs): print(f"[开始执行] {func.__name__}") result = func(*args, **kwargs) print(f"[执行完毕] {func.__name__}") return result return wrapper用于为任意任务函数添加前后日志。
定义可动态调用的任务(反射机制)
class TaskCenter: @log_task def download(self): print("正在下载文件...") @log_task def upload(self): print("正在上传文件...") @log_task def clean(self): print("正在清理文件...")现在任务中心拥有 3 个可执行任务。
反射可以让我们根据任务名字自动找到对应函数并执行:
task_center = TaskCenter() task_name = "download" getattr(task_center, task_name)()
用迭代器实现“任务队列”
任务队列类:
class TaskQueue: def __init__(self, tasks): self.tasks = tasks self.index = 0 def __iter__(self): return self def __next__(self): if self.index < len(self.tasks): task = self.tasks[self.index] self.index += 1 return task else: raise StopIteration使用:
queue = TaskQueue(["download", "upload", "clean"]) for task in queue: print("任务:", task)
综合任务执行器:所有能力整合
class TaskExecutor: def __init__(self, task_center): self.task_center = task_center def run(self, tasks): for task_name in tasks: if hasattr(self.task_center, task_name): func = getattr(self.task_center, task_name) func() # 自动带日志(因为被装饰器增强) else: print(f"未知任务:{task_name}")
最终运行代码(可直接复制)
# 任务中心(带装饰器) class TaskCenter: @log_task def download(self): print("正在下载文件...") @log_task def upload(self): print("正在上传文件...") @log_task def clean(self): print("正在清理文件...") # 任务队列 queue = TaskQueue(["download", "upload", "clean"]) # 执行器 executor = TaskExecutor(TaskCenter()) executor.run(queue)运行结果示例:
[开始执行] download 正在下载文件... [执行完毕] download [开始执行] upload 正在上传文件... [执行完毕] upload [开始执行] clean 正在清理文件... [执行完毕] clean
本章总结
本综合案例整合了本 PDF 中所有关键高阶技巧:
技巧 用法 闭包 保存状态(执行次数) 装饰器 自动添加日志功能 迭代器 实现任务队列 反射 根据字符串动态执行任务 动态函数 灵活扩展任务系统 高阶编程思想 开放可扩展的结构设计 一句话总结:
本案例展示了 Python 高阶技巧如何组合成强大的工程化能力,体现了 Python 动态语言的真正威力。