Python 闭包和装饰器教程
目录
Python 闭包 (Closures)
闭包的定义
闭包 (Closure) 是指一个函数能够记住它所在的作用域的变量,即使当这个函数在其定义环境之外被调用时,也能访问这些变量。更准确地说,闭包是由函数及其相关的引用环境组合而成的实体。
闭包必须同时满足三个条件:
- 必须有一个内部函数(嵌套函数)
- 内部函数必须引用外部函数中的变量
- 外部函数必须返回内部函数
闭包的原理和内存模型
要理解闭包,首先需要了解 Python 的作用域和名字查找规则 (LEGB):
- Local (本地): 函数内部的命名空间
- Enclosing (封闭): 外部嵌套函数的命名空间
- Global (全局): 模块的命名空间
- Built-in (内置): Python 内置模块的命名空间
闭包的内存模型解释:
def outer_func(x):
# x 存储在 outer_func 的本地作用域
def inner_func(y):
# 当 inner_func 被调用时,它可以访问 x
return x + y
# 返回函数对象,而不是调用函数
return inner_func
当 outer_func
被调用时:
- 创建
outer_func
的本地作用域,变量x
被绑定到传入的值 - 定义内部函数
inner_func
- 关键点:Python 发现
inner_func
使用了变量x
,这个变量不在inner_func
的本地作用域,而是在外部函数outer_func
的作用域 - Python 创建一个闭包,将
x
的引用与inner_func
绑定 - 当
outer_func
返回时,通常其本地作用域会被销毁,但因为inner_func
仍然引用x
,所以x
不会被垃圾回收 - 这个返回的函数对象包含函数代码和自由变量的引用(闭包)
等价理解方式:
闭包可以理解为一个特殊的对象,它包含:
- 函数代码
- 环境变量的快照
如果用类来模拟闭包,可以写成:
class ClosureSimulator:
def __init__(self, x):
self.x = x # 存储外部变量
def __call__(self, y):
return self.x + y # 使用存储的变量和参数执行操作
# 闭包函数
def outer_func(x):
def inner_func(y):
return x + y
return inner_func
# 等价的类实现
closure_func = outer_func(10)
closure_obj = ClosureSimulator(10)
print(closure_func(5)) # 15
print(closure_obj(5)) # 15
这个等价形式帮助我们理解闭包是如何"记住"其创建环境的变量的。
创建闭包
以下是创建闭包的基本结构:
def 外部函数(外部参数):
# 外部函数的代码
def 内部函数(内部参数):
# 内部函数使用外部参数
结果 = 外部参数 + 内部参数
return 结果
# 返回内部函数,而不是调用它
return 内部函数
一个具体的例子:
def make_multiplier(factor):
def multiply(number):
return number * factor
return multiply
# 创建两个闭包函数
double = make_multiplier(2)
triple = make_multiplier(3)
# 使用闭包函数
print(double(5)) # 输出: 10
print(triple(5)) # 输出: 15
在这个例子中,double
和 triple
都是闭包。它们都"记住"了创建时各自的 factor
值。
闭包的应用场景
1. 函数工厂
创建专用函数的工厂:
def power_function_factory(exponent):
def power(base):
return base ** exponent
return power
square = power_function_factory(2)
cube = power_function_factory(3)
print(square(4)) # 16
print(cube(4)) # 64
2. 数据隐藏和封装
创建带有私有状态的函数:
def counter_factory():
count = 0 # 私有变量
def counter():
nonlocal count
count += 1
return count
return counter
counter = counter_factory()
print(counter()) # 1
print(counter()) # 2
print(counter()) # 3
这里,count
变量被封装在闭包中,无法直接访问,只能通过返回的函数间接操作。
3. 实现装饰器
闭包是装饰器实现的基础:
def simple_decorator(func):
def wrapper():
print("函数将要执行")
func() # 调用原始函数
print("函数已执行完毕")
return wrapper
@simple_decorator
def hello():
print("Hello, World!")
# 调用装饰后的函数
hello()
# 下一个例子
import time
def time_master(func):
def call_func():
print("开始运行程序 ... ”)
start = time. time ()
func()
stop = time. time ()
print("结束程序运行 .. ”)
print(f"一共耗费了{(stop-start) :. 2f}秒。”)
return call_func
def myfunc():
time.sleep(2)
print("I love FishC.")
myfunc = time_master(myfunc)
myfunc ()
闭包的注意事项
1. 修改外部变量需要使用 nonlocal
在 Python 3 中,如果需要在闭包内部修改外部函数的变量,必须使用 nonlocal
关键字:
def outer():
x = 10
def inner():
nonlocal x # 声明使用外部函数的变量
x += 1 # 修改外部变量
return x
return inner
func = outer()
print(func()) # 11
print(func()) # 12
不使用 nonlocal
的话,Python 会将 x
视为内部函数的本地变量,导致 “UnboundLocalError”。
2. 闭包捕获的是变量引用而非变量值
闭包捕获的是变量的引用,而不是变量的值。如果在闭包创建后修改了被引用的变量,闭包会使用修改后的值:
def confusing_closure():
funcs = []
for i in range(3):
def func():
return i
funcs.append(func)
return funcs
funcs = confusing_closure()
# 所有函数都返回 2,因为它们引用的是同一个 i 变量,而该变量的最终值为 2
print([f() for f in funcs]) # [2, 2, 2]
解决方法:使用默认参数捕获当前值:
def correct_closure():
funcs = []
for i in range(3):
def func(i=i): # 将当前的 i 作为默认参数值
return i
funcs.append(func)
return funcs
funcs = correct_closure()
print([f() for f in funcs]) # [0, 1, 2]
Python 装饰器 (Decorators)
装饰器的定义
装饰器 (Decorator) 是一个接收函数作为输入并返回新函数的可调用对象(通常是函数)。装饰器的目的是通过使用现有代码"装饰"(或增强)函数或方法,从而扩展其功能。
装饰器遵循了开放封闭原则:对扩展开放,对修改封闭。这意味着您可以扩展函数的行为,而无需修改函数本身的代码。
装饰器语法糖及其等价形式
Python 提供了一个特殊的语法糖 @decorator
来应用装饰器:
@decorator
def function():
pass
等价形式:
def function():
pass
function = decorator(function)
这两种形式完全相同。@decorator
语法糖实际上就是在定义函数后立即用装饰器包装这个函数。
具体例子:
def my_decorator(func):
def wrapper():
print("前置代码")
func()
print("后置代码")
return wrapper
# 使用装饰器语法糖
@my_decorator
def say_hello():
print("Hello!")
# 等价于
def say_hello_no_sugar():
print("Hello!")
say_hello_no_sugar = my_decorator(say_hello_no_sugar)
嵌套函数与双括号调用
装饰器通常使用嵌套函数实现,这导致了双括号调用的形式。让我们详细解释这一过程:
def outer(func):
def inner(*args, **kwargs):
# 增强功能
return func(*args, **kwargs)
return inner
@outer
def function(arg):
return arg
# 调用装饰后的函数
result = function("hello")
双括号运行过程解析:
-
第一对括号:
outer(function)
outer
接收function
作为参数- 定义了
inner
函数 - 返回
inner
函数的引用
-
这使得装饰后的
function
实际上是inner
函数 -
第二对括号:
function("hello")
(实际上是inner("hello")
)- 当调用
function("hello")
时,实际上是在调用inner("hello")
inner
内部调用原始的function
函数,并可能在前后添加新功能
- 当调用
等价形式:
使用变量名来跟踪每一步的函数引用:
def outer(func):
def inner(*args, **kwargs):
print("函数执行前")
result = func(*args, **kwargs)
print("函数执行后")
return result
return inner
# 定义原始函数
def original_function(arg):
print(f"原始函数,参数: {arg}")
return arg
# 手动应用装饰器
step1 = outer # step1 引用装饰器函数
step2 = step1(original_function) # step2 引用 inner 函数
step3 = step2("hello") # step3 是调用 inner("hello") 的结果
# 这与以下调用等价
decorated_function = outer(original_function)
result = decorated_function("hello")
# 使用语法糖的最终形式
@outer
def function(arg):
print(f"原始函数,参数: {arg}")
return arg
result = function("hello")
这个等价形式清楚地展示了装饰器如何通过嵌套函数和双括号调用来工作。
带参数的装饰器
有时我们需要装饰器本身也能接受参数。这会导致更复杂的嵌套结构:
def repeat(n=1):
def decorator(func):
def wrapper(*args, **kwargs):
result = None
for _ in range(n):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def say_hello(name):
print(f"Hello, {name}!")
return name
等价形式:
def repeat(n=1):
# 第一对括号: repeat(3)
def decorator(func):
# 第二对括号: repeat(3)(say_hello)
def wrapper(*args, **kwargs):
# 第三对括号: repeat(3)(say_hello)("World")
result = None
for _ in range(n):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
# 不使用语法糖
def say_hello(name):
print(f"Hello, {name}!")
return name
# 手动应用
step1 = repeat(3) # 返回 decorator 函数
step2 = step1(say_hello) # 返回 wrapper 函数
step3 = step2("World") # 执行 wrapper,它会调用原始的 say_hello 三次
# 这与以下调用等价
decorated_function = repeat(3)(say_hello) # 嵌套函数的调用方式之一
result = decorated_function("World")
# 最终的语法糖形式
@repeat(3)
def say_hello(name):
print(f"Hello, {name}!")
return name
result = say_hello("World")
这个例子中有三层函数:
repeat(n)
- 接收装饰器参数decorator(func)
- 接收要装饰的函数wrapper(*args, **kwargs)
- 包装原始函数调用
多重装饰器
可以为函数应用多个装饰器,它们会从下到上(从内到外)执行:
@decorator1
@decorator2
@decorator3
def function():
pass
等价形式:
def function():
pass
# 从内到外应用装饰器
function = decorator1(decorator2(decorator3(function)))
实际例子:
def bold(func):
def wrapper(*args, **kwargs):
return f"<b>{func(*args, **kwargs)}</b>"
return wrapper
def italic(func):
def wrapper(*args, **kwargs):
return f"<i>{func(*args, **kwargs)}</i>"
return wrapper
def underline(func):
def wrapper(*args, **kwargs):
return f"<u>{func(*args, **kwargs)}</u>"
return wrapper
@bold
@italic
@underline
def text(content):
return content
# 使用装饰后的函数
print(text("Hello")) # 输出: <b><i><u>Hello</u></i></b>
# 等价于:
def text_no_sugar(content):
return content
decorated = bold(italic(underline(text_no_sugar)))
print(decorated("Hello")) # 输出: <b><i><u>Hello</u></i></b>
在这个例子中,装饰器按以下顺序应用:
- 先应用
@underline
- 然后应用
@italic
- 最后应用
@bold
装饰器的应用场景
1. 执行时间测量
import time
import functools
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"函数 {func.__name__} 执行时间: {end_time - start_time:.4f} 秒")
return result
return wrapper
@timer
def slow_function(n):
"""这是一个耗时的函数"""
time.sleep(n) # 模拟耗时操作
return n
slow_function(1) # 输出: 函数 slow_function 执行时间: 1.xxxx 秒
2. 日志记录
import functools
import logging
logging.basicConfig(level=logging.INFO)
def log(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
logging.info(f"调用函数: {func.__name__} 参数: {args}, {kwargs}")
result = func(*args, **kwargs)
logging.info(f"函数 {func.__name__} 返回: {result}")
return result
return wrapper
@log
def add(x, y):
"""加法函数"""
return x + y
add(3, 5)
3. 缓存结果 (记忆化)
import functools
def memoize(func):
cache = {}
@functools.wraps(func)
def wrapper(*args):
if args in cache:
return cache[args]
result = func(*args)
cache[args] = result
return result
return wrapper
@memoize
def fibonacci(n):
"""计算斐波那契数列第n项"""
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(35)) # 不使用缓存会非常慢,使用缓存后很快
4. 访问控制和身份验证
import functools
def require_auth(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
if not is_authenticated(): # 假设这是一个检查用户是否已认证的函数
return "未授权访问"
return func(*args, **kwargs)
return wrapper
def is_authenticated():
# 这里应该是实际的认证逻辑
return False
@require_auth
def protected_resource():
return "敏感数据"
print(protected_resource()) # 输出: 未授权访问
高级主题
装饰类的装饰器
装饰器不仅可以装饰函数,也可以装饰类:
def add_greeting(cls):
cls.greet = lambda self: f"Hello from {self.__class__.__name__}"
return cls
@add_greeting
class Person:
def __init__(self, name):
self.name = name
person = Person("张三")
print(person.greet()) # 输出: Hello from Person
保留原始函数信息
使用装饰器时,原始函数的元数据(如函数名、文档字符串等)会丢失。使用 functools.wraps
可以解决这个问题:
import functools
def my_decorator(func):
@functools.wraps(func) # 保留原函数的元数据
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
内置装饰器
Python 提供了一些内置的装饰器:
- @property - 将方法转换为属性
- @classmethod - 创建类方法(接收类作为第一个参数)
- @staticmethod - 创建静态方法(不接收特殊的第一个参数)
- @abstractmethod - 声明抽象方法(需要导入 abc 模块)
示例:
class Temperature:
def __init__(self, celsius=0):
self._celsius = celsius
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
self._celsius = value
@property
def fahrenheit(self):
return self._celsius * 9/5 + 32
@fahrenheit.setter
def fahrenheit(self, value):
self._celsius = (value - 32) * 5/9
@classmethod
def from_fahrenheit(cls, value):
return cls(celsius=(value - 32) * 5/9)
@staticmethod
def is_freezing(temp_c):
return temp_c <= 0
# 使用
temp = Temperature(25)
print(temp.celsius) # 25
print(temp.fahrenheit) # 77.0
temp.fahrenheit = 68
print(temp.celsius) # 20.0
temp2 = Temperature.from_fahrenheit(41)
print(temp2.celsius) # 5.0
print(Temperature.is_freezing(0)) # True
类作为装饰器
类也可以用作装饰器,只需要实现 __call__
方法:
class CountCalls:
def __init__(self, func):
functools.update_wrapper(self, func)
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"函数 {self.func.__name__} 被调用了 {self.count} 次")
return self.func(*args, **kwargs)
@CountCalls
def say_hello(name):
return f"Hello, {name}!"
print(say_hello("World")) # 函数 say_hello 被调用了 1 次
print(say_hello("Python")) # 函数 say_hello 被调用了 2 次
这个例子中,CountCalls
类的实例作为装饰器,每次函数被调用时都会记录并显示调用次数。
闭包和装饰器的示例代码
"""
Python 闭包和装饰器示例代码
演示闭包和装饰器的工作原理和应用场景
"""
import time
import functools
from datetime import datetime
print("\n" + "="*50)
print("Python 闭包和装饰器示例")
print("="*50)
# =====================================================================
# 闭包基础示例
# =====================================================================
print("\n1. 闭包基础示例")
def make_multiplier(factor):
"""创建一个乘法闭包"""
def multiply(number):
"""内部函数,执行乘法运算"""
return number * factor
# 输出闭包的内部状态,用于演示
print(f"创建了一个乘法器,factor={factor}")
print(f"multiply.__closure__={multiply.__closure__}")
return multiply
# 创建闭包
double = make_multiplier(2)
triple = make_multiplier(3)
# 使用闭包
print(f"double(5) = {double(5)}") # 10
print(f"triple(5) = {triple(5)}") # 15
# 查看闭包的"记忆"
print(f"double.__closure__[0].cell_contents = {double.__closure__[0].cell_contents}") # 2
print(f"triple.__closure__[0].cell_contents = {triple.__closure__[0].cell_contents}") # 3
# =====================================================================
# 模拟闭包的类实现(等价形式)
# =====================================================================
print("\n2. 模拟闭包的类实现")
class MultiplierClass:
"""使用类模拟闭包的行为"""
def __init__(self, factor):
self.factor = factor
def __call__(self, number):
return number * self.factor
# 创建实例(等价于闭包)
double_class = MultiplierClass(2)
triple_class = MultiplierClass(3)
# 使用实例
print(f"double_class(5) = {double_class(5)}") # 10
print(f"triple_class(5) = {triple_class(5)}") # 15
# =====================================================================
# 闭包的应用:计数器
# =====================================================================
print("\n3. 闭包应用:计数器")
def make_counter():
"""创建一个计数器闭包"""
count = 0
def counter():
nonlocal count
count += 1
return count
return counter
counter1 = make_counter()
counter2 = make_counter()
for _ in range(3):
print(f"counter1: {counter1()}")
for _ in range(2):
print(f"counter2: {counter2()}")
# =====================================================================
# nonlocal 关键字的重要性
# =====================================================================
print("\n4. nonlocal 关键字的重要性")
def wrong_counter():
count = 0
def counter():
# 没有 nonlocal,count 被视为局部变量
# count += 1 # 将导致 UnboundLocalError
return count
return counter
def correct_counter():
count = 0
def counter():
nonlocal count # 声明 count 为外部变量
count += 1
return count
return counter
try:
# 会引发 UnboundLocalError 的代码已被注释,这里只进行演示
print("如果尝试在不使用 nonlocal 的情况下修改 count,会引发 UnboundLocalError")
c1 = correct_counter()
print(f"正确的计数器: {c1()}, {c1()}")
except UnboundLocalError as e:
print(f"错误: {e}")
# =====================================================================
# 闭包与循环变量
# =====================================================================
print("\n5. 闭包与循环变量")
def create_functions
总结
闭包:
- 闭包是由函数及其引用环境组成的实体
- 闭包允许函数"记住"其定义环境中的变量
- 闭包主要用于数据隐藏、函数工厂和维护状态
装饰器:
- 装饰器是扩展函数或类功能的工具
- Python 的
@decorator
语法糖使装饰器使用更加简洁 - 装饰器实现了开放封闭原则:对扩展开放,对修改封闭
- 装饰器常用于日志记录、性能测量、访问控制等横切关注点