装饰器与生成器
# 装饰器与生成器
在Python的进阶道路上,装饰器(Decorators)和生成器(Generators)是两个极其强大且富有表现力的特性。它们不仅能让你的代码变得更加简洁、优雅,还能在处理复杂任务和大数据时显著提升性能。掌握它们是区分Python初学者和有经验开发者的一个重要标志。
# 1. 装饰器 (Decorators)
装饰器本质上是一个接收函数作为参数,并返回一个新函数的高阶函数。 它允许我们在不修改原函数代码和调用方式的前提下,为该函数动态地添加新的功能(如日志记录、性能测试、事务处理、权限校验等)。
# 1.1 核心思想:函数是“一等公民”
理解装饰器的前提是回顾并深刻理解Python中函数作为“一等公民”的特性:
- 可以被赋值给变量:
my_func = some_function
- 可以作为参数传递给其他函数:
process(some_function)
- 可以作为其他函数的返回值:
return some_function
这使得通过一个“包装”函数来扩展另一个函数的功能成为可能。
# 1.2 装饰器的基本实现
我们从一个简单的例子开始,手动实现一个装饰器来理解其工作原理。
def my_decorator(func_to_decorate):
"""这是一个简单的装饰器函数"""
def wrapper():
"""这是包装函数,它包含了新功能和对原函数的调用"""
print("--- 函数开始执行,我是新添加的功能 ---")
func_to_decorate() # 调用原始函数
print("--- 函数执行结束,我也是新添加的功能 ---")
return wrapper # 返回包装后的新函数
def say_hello():
"""一个简单的问候函数"""
print("你好,世界!")
# 手动进行装饰
# 1. 将原函数传递给装饰器
# 2. 装饰器返回一个包装后的新函数
# 3. 将新函数重新赋值给原函数名
say_hello = my_decorator(say_hello)
# 现在调用 say_hello(),执行的其实是 wrapper() 函数
say_hello()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
输出:
--- 函数开始执行,我是新添加的功能 ---
你好,世界!
--- 函数执行结束,我也是新添加的功能 ---
2
3
# 1.3 语法糖:@
符号
Python 提供了 @
语法糖,让装饰器的使用更加直观和优雅,它能自动完成上面手动装饰的步骤。
@my_decorator
def say_goodbye():
print("再见,世界!")
say_goodbye()
# 上面的 @my_decorator 写法完全等价于:
# say_goodbye = my_decorator(say_goodbye)
2
3
4
5
6
7
8
# 1.4 装饰带参数和返回值的函数
如果原始函数有参数和返回值,包装函数(wrapper)也必须能够接收这些参数并返回其结果,否则会丢失信息。我们使用 *args
和 **kwargs
来接收任意参数。
def timing_decorator(func):
import time
def wrapper(*args, **kwargs):
start_time = time.time()
# 将参数传递给原函数,并接收返回值
result = func(*args, **kwargs)
end_time = time.time()
print(f"函数 {func.__name__!r} 执行耗时: {end_time - start_time:.4f} 秒")
return result # 将原函数的执行结果返回
return wrapper
@timing_decorator
def calculate_sum(n):
"""计算从1到n的和"""
total = 0
for i in range(1, n + 1):
total += i
return total
sum_result = calculate_sum(1000000)
print(f"计算结果: {sum_result}")
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 1.5 保持元信息:functools.wraps
的重要性
你会发现,上例中 calculate_sum
函数被装饰后,它的元信息(如函数名 __name__
、文档字符串 __doc__
)丢失了,变成了包装函数 wrapper
的信息。
print(calculate_sum.__name__) # 输出: 'wrapper'
print(calculate_sum.__doc__) # 输出: None
2
为了解决这个问题,Python 的 functools
模块提供了 wraps
装饰器。只需将它应用到你的包装函数上即可。
import time
from functools import wraps
def timing_decorator_fixed(func):
@wraps(func) # 关键步骤:将被装饰函数的元信息复制到 wrapper 函数上
def wrapper(*args, **kwargs):
# ... (逻辑同上)
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"函数 {func.__name__!r} 执行耗时: {end_time - start_time:.4f} 秒")
return result
return wrapper
@timing_decorator_fixed
def calculate_power(base, exponent):
"""计算一个数的幂"""
return base ** exponent
print(calculate_power.__name__) # 输出: 'calculate_power'
print(calculate_power.__doc__) # 输出: '计算一个数的幂'
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
最佳实践:始终在你自定义的装饰器内部,使用 @wraps(func)
来装饰你的包装函数。
# 1.6 带参数的装饰器
如果想让装饰器本身接收参数(例如 @repeat(num_times=3)
),需要再增加一层函数嵌套。
def repeat(num_times): # 1. 最外层接收装饰器参数
def decorator(func): # 2. 第二层接收被装饰函数
@wraps(func)
def wrapper(*args, **kwargs): # 3. 最内层是真正的包装逻辑
last_result = None
for _ in range(num_times):
last_result = func(*args, **kwargs)
return last_result
return wrapper
return decorator
@repeat(num_times=3)
def greet(name):
print(f"你好, {name}!")
greet("张三")
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 2. 生成器 (Generators)
生成器是 Python 中一种特殊的迭代器,它允许你“惰性地”生成一个值序列。与一次性计算并返回整个列表的普通函数不同,生成器一次只产生一个值,并在两次产生之间“暂停”其状态,极大地节省了内存。
# 2.1 创建生成器:yield
关键字
只要函数中包含 yield
关键字,它就不再是普通函数,而是一个生成器函数。调用生成器函数会返回一个生成器对象。
def simple_generator():
print("生成器启动,即将产出第一个值")
yield 1
print("继续执行,即将产出第二个值")
yield 2
print("再次继续,即将产出第三个值")
yield 3
print("生成器执行结束")
# 调用生成器函数,得到一个生成器对象
gen = simple_generator()
# 生成器是迭代器,可以通过 next() 函数获取下一个值
print(f"获取值: {next(gen)}")
print(f"获取值: {next(gen)}")
# 也可以像迭代器一样在 for 循环中使用
for value in gen:
print(f"循环中获取值: {value}")
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
当生成器函数执行到 yield
时,它会“暂停”并将 yield
后面的值返回给调用者。当 next()
再次被调用时,它会从上次暂停的地方继续执行,直到遇到下一个 yield
或函数执行完毕(此时会自动引发 StopIteration
异常)。
# 2.2 生成器表达式 (Generator Expressions)
类似于列表推导式,Python 提供了生成器表达式,用于快速创建简单的生成器。语法上,只需将列表推导式的 []
替换为 ()
。
# 列表推导式:立即创建完整列表,占用内存
list_comp = [i * i for i in range(1000)]
# print(list_comp)
# 生成器表达式:返回一个生成器对象,不立即计算,几乎不占内存
gen_expr = (i * i for i in range(1000))
print(gen_expr) # 输出: <generator object <genexpr> at 0x...>
# 只有在遍历生成器时,值才会被逐个计算和产出
# for square in gen_expr:
# print(square, end=' ')
2
3
4
5
6
7
8
9
10
11
# 2.3 生成器的优势与应用场景
生成器的核心优势在于内存效率和惰性求值。
处理大数据或无限序列 如果你需要处理一个巨大的文件,没必要一次性将它全部读入内存。
def read_large_file(file_path): """逐行读取大文件,不占用大量内存""" with open(file_path, 'r', encoding='utf-8') as f: for line in f: yield line.strip() # 即使文件有几GB大,内存占用也非常小 # for log_entry in read_large_file("huge_log.txt"): # if "ERROR" in log_entry: # print(log_entry)
1
2
3
4
5
6
7
8
9
10构建数据处理管道 可以将多个生成器连接起来,形成一个高效的数据处理流水线。数据在管道中是“流式”处理的,每个元素只在需要时才被计算。
def get_numbers(n): for i in range(n): yield i def square_numbers(numbers_gen): for num in numbers_gen: yield num * num def filter_evens(squared_gen): for num in squared_gen: if num % 2 == 0: yield num # 将生成器连接起来,形成管道 pipeline = filter_evens(square_numbers(get_numbers(10))) # 只有在迭代时,数据才会被逐个地、按需地流过整个管道 for even_square in pipeline: print(even_square) # 输出 0, 4, 16, 36, 64
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19协程(高级用法) 生成器的
send()
方法使其能够接收外部传入的值,这是构建协程(一种用户态的轻量级线程)的基础,在异步编程中扮演着关键角色。这部分内容更为高深,通常在学习异步IO(如asyncio
)时会深入探讨。