
你有没有想过,为什么 Python 中的函数可以像普通变量一样传递?为什么可以在函数内部定义另一个函数?为什么可以用 @ 符号修饰函数并增强其功能?这些问题的答案都与闭包和装饰器有关。
闭包和装饰器是 Python 中最具表现力和灵活性的特性之一,它们是高阶函数的典型应用,广泛应用于:
本文将从基础概念出发,系统讲解闭包和装饰器的原理、实现方法和调用方式,并结合自带装饰器和实战示例,带你全面掌握这两个核心特性。
闭包是一种函数对象,它包含了函数的定义和函数执行时的环境。换句话说,闭包可以 “记住” 函数定义时所在的作用域,即使函数在作用域外部被调用,也可以访问该作用域中的变量。
闭包通常由以下两部分组成:
要创建一个闭包,必须满足以下条件:
# 外部函数
def outer_function(x):
# 自由变量
a = 10
# 内部函数
def inner_function(y):
# 引用外部函数的变量 x 和 a
return x + y + a
# 返回内部函数
return inner_function
# 创建闭包
closure = outer_function(5)
# 调用闭包
result = closure(3)
print(result) # 输出:18在上面的示例中,当 outer_function(5) 被调用时,它创建了一个作用域,其中包含变量 x=5 和 a=10。然后它返回 inner_function 对象。当 closure(3) 被调用时,inner_function 仍然可以访问 x=5 和 a=10,即使 outer_function 已经执行完毕。
def counter():
count = 0
def increment():
nonlocal count # 使用 nonlocal 关键字声明变量为非局部变量
count += 1
return count
return increment
# 创建计数器
count1 = counter()
print(count1()) # 输出:1
print(count1()) # 输出:2
print(count1()) # 输出:3
# 创建另一个计数器
count2 = counter()
print(count2()) # 输出:1def power(n):
def calculate(x):
return x ** n
return calculate
# 创建平方函数
square = power(2)
print(square(3)) # 输出:9
# 创建立方函数
cube = power(3)
print(cube(3)) # 输出:27装饰器是一种用于修改或增强函数或类功能的高阶函数,它接受一个函数作为参数,并返回一个新的函数,新函数包含了原函数的功能和装饰器添加的功能。
# 定义装饰器
def decorator(func):
def wrapper():
# 在原函数执行前添加的功能
print("Before function execution")
# 执行原函数
func()
# 在原函数执行后添加的功能
print("After function execution")
# 返回新函数
return wrapper
# 使用装饰器
@decorator
def hello():
print("Hello, World!")
# 调用函数
hello()装饰器的工作原理是函数的嵌套和闭包:
@decorator 时,Python 会将被装饰的函数 hello 作为参数传递给 decorator 函数decorator 函数返回 wrapper 函数hello() 时,实际上是调用 wrapper() 函数当一个函数被多个装饰器装饰时,装饰器的执行顺序是从下到上。
def decorator1(func):
def wrapper():
print("Decorator 1 Before")
func()
print("Decorator 1 After")
return wrapper
def decorator2(func):
def wrapper():
print("Decorator 2 Before")
func()
print("Decorator 2 After")
return wrapper
@decorator1
@decorator2
def hello():
print("Hello, World!")
hello()Decorator 1 Before
Decorator 2 Before
Hello, World!
Decorator 2 After
Decorator 1 After如果被装饰的函数带有参数,需要在 wrapper 函数中接收并传递这些参数。
def decorator(func):
def wrapper(*args, **kwargs):
print("Before function execution")
result = func(*args, **kwargs)
print("After function execution")
return result
return wrapper
@decorator
def add(a, b):
return a + b
result = add(1, 2)
print(result) # 输出:3如果被装饰的函数有返回值,需要在 wrapper 函数中捕获并返回。
def decorator(func):
def wrapper(*args, **kwargs):
print("Before function execution")
result = func(*args, **kwargs)
print("After function execution")
return result
return wrapper
@decorator
def multiply(a, b):
return a * b
result = multiply(3, 4)
print(result) # 输出:12如果装饰器需要传递参数,需要在原装饰器外再嵌套一层函数。
def decorator_with_args(prefix):
def decorator(func):
def wrapper(*args, **kwargs):
print(f"{prefix} Before function execution")
result = func(*args, **kwargs)
print(f"{prefix} After function execution")
return result
return wrapper
return decorator
@decorator_with_args("DEBUG:")
def divide(a, b):
return a / b
result = divide(10, 2)
print(result) # 输出:5使用装饰器后,原函数的元信息(如函数名、文档字符串等)会丢失,可以使用 functools.wraps 保留这些信息。
from functools import wraps
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
"""Wrapper function"""
result = func(*args, **kwargs)
return result
return wrapper
@decorator
def hello():
"""Hello function"""
print("Hello, World!")
print(f"Function name: {hello.__name__}") # 输出:hello
print(f"Function docstring: {hello.__doc__}") # 输出:Hello function除了函数装饰器,还可以使用类装饰器,将装饰器封装为一个类。
class Decorator:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print("Before function execution")
result = self.func(*args, **kwargs)
print("After function execution")
return result
@Decorator
def subtract(a, b):
return a - b
result = subtract(5, 3)
print(result) # 输出:2Python 提供了一些内置的装饰器,用于简化开发。
@staticmethod:静态方法静态方法是属于类的方法,不依赖于类的实例,可以直接通过类名调用。
class Math:
@staticmethod
def add(a, b):
return a + b
# 直接通过类名调用
print(Math.add(1, 2)) # 输出:3
# 通过实例调用
math = Math()
print(math.add(3, 4)) # 输出:7@classmethod:类方法类方法是属于类的方法,第一个参数是 cls(指向类本身),可以访问和修改类属性。
class Counter:
count = 0
def __init__(self):
Counter.count += 1
@classmethod
def get_count(cls):
return cls.count
# 创建实例
c1 = Counter()
c2 = Counter()
c3 = Counter()
# 调用类方法
print(Counter.get_count()) # 输出:3@property:属性装饰器属性装饰器是将方法转换为属性,让方法可以像属性一样访问,主要用于封装计算属性。
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value < 0:
raise ValueError("Radius cannot be negative")
self._radius = value
@property
def area(self):
import math
return math.pi * self._radius ** 2
# 使用示例
circle = Circle(5)
print(circle.radius) # 输出:5
print(circle.area) # 输出:78.53981633974483
# 修改半径
circle.radius = 10
print(circle.radius) # 输出:10
print(circle.area) # 输出:314.1592653589793@functools.lru_cache:缓存装饰器缓存装饰器是用于缓存函数的结果,避免重复计算,提高函数的运行效率。
from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
# 第一次调用,需要计算
print(fibonacci(10)) # 输出:55
# 第二次调用,直接从缓存中获取
print(fibonacci(10)) # 输出:55@functools.wraps:保留元信息装饰器@functools.wraps 用于保留原函数的元信息,如函数名、文档字符串等。
from functools import wraps
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@decorator
def hello():
"""Hello function"""
pass
print(hello.__name__) # 输出:hello
print(hello.__doc__) # 输出:Hello functionfrom functools import wraps
import datetime
def log_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
# 记录请求时间
request_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# 记录请求信息
print(f"[LOG] {request_time} - Function: {func.__name__}, Args: {args}, Kwargs: {kwargs}")
# 执行原函数
result = func(*args, **kwargs)
# 记录响应信息
print(f"[LOG] {request_time} - Function: {func.__name__}, Result: {result}")
return result
return wrapper
@log_decorator
def add(a, b):
return a + b
@log_decorator
def multiply(a, b):
return a * b
add(1, 2)
multiply(3, 4)from functools import wraps
# 合法的 API 密钥
VALID_API_KEYS = {"sk-123456", "ak-789012"}
def api_key_required(func):
@wraps(func)
def wrapper(*args, **kwargs):
# 假设 API 密钥是通过 kwargs 传递的
if "api_key" not in kwargs:
return {"error": "Missing API key"}, 401
api_key = kwargs.pop("api_key")
if api_key not in VALID_API_KEYS:
return {"error": "Invalid API key"}, 401
# 调用原函数
return func(*args, **kwargs)
return wrapper
@api_key_required
def get_user_info(user_id):
return {"user_id": user_id, "name": "John Doe"}, 200
# 测试
print(get_user_info(123, api_key="sk-123456")) # 输出:({'user_id': 123, 'name': 'John Doe'}, 200)
print(get_user_info(123, api_key="invalid")) # 输出:({'error': 'Invalid API key'}, 401)from functools import wraps
from collections import defaultdict
import time
# 限流字典
request_count = defaultdict(list)
def rate_limit(limit=10, window=60):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
# 获取用户 ID(假设在 kwargs 中)
user_id = kwargs.get("user_id")
if not user_id:
return {"error": "Missing user ID"}, 400
current_time = time.time()
# 清除过期的请求
request_count[user_id] = [t for t in request_count[user_id] if current_time - t < window]
# 检查请求次数
if len(request_count[user_id]) >= limit:
return {"error": "Rate limit exceeded"}, 429
# 记录请求
request_count[user_id].append(current_time)
# 调用原函数
return func(*args, **kwargs)
return wrapper
return decorator
@rate_limit(limit=3, window=10)
def send_message(user_id, message):
return {"status": "success", "message": "Message sent"}, 200
# 测试
for i in range(5):
print(send_message(user_id="123", message="Hello"))
time.sleep(2)from functools import wraps
def cache_decorator(func):
cache = {}
@wraps(func)
def wrapper(*args, **kwargs):
# 生成缓存键
cache_key = str(args) + str(sorted(kwargs.items()))
# 检查缓存
if cache_key in cache:
print(f"[CACHE] Using cached result for {args}, {kwargs}")
return cache[cache_key]
# 执行原函数
result = func(*args, **kwargs)
# 保存到缓存
cache[cache_key] = result
print(f"[CACHE] Saving result for {args}, {kwargs}")
return result
return wrapper
@cache_decorator
def expensive_calculation(a, b):
import time
time.sleep(2) # 模拟耗时计算
return a + b
# 第一次调用
print(expensive_calculation(1, 2)) # 输出:3
# 第二次调用
print(expensive_calculation(1, 2)) # 输出:3装饰器的顺序非常重要,应该 ** 按照 “安全→限流→日志→业务→统计→异常”** 的顺序装饰:
虽然装饰器很强大,但不要过度装饰,过多的装饰器会增加代码的复杂度和运行时间。
对于频繁调用且结果不变的函数,可以使用 functools.lru_cache 装饰器缓存结果。
将装饰器的配置参数化,提高装饰器的灵活性。
def rate_limit(limit=10, window=60):
def decorator(func):
def wrapper(*args, **kwargs):
# 限流逻辑
pass
return wrapper
return decorator
# 使用参数化装饰器
@rate_limit(limit=5, window=30)
def call_api():
pass对于复杂的装饰器,可以使用类装饰器,提高代码的可读性和可维护性。
在使用装饰器之前,应该充分测试装饰器的功能,确保它不会影响原函数的正常运行。
问题:装饰器没有传递被装饰函数的参数。解决方案:在 wrapper 函数中使用 *args 和 **kwargs 接收和传递参数。
问题:装饰器修改了原函数的名称、文档字符串等元信息。解决方案:使用 functools.wraps 保留原函数的元信息。
问题:装饰器的执行顺序不符合预期。解决方案:注意装饰器的顺序,从下到上执行。
问题:装饰器本身的参数传递错误。解决方案:使用三层嵌套的装饰器处理装饰器本身的参数。
问题:装饰器导致函数运行速度变慢。解决方案:
闭包和装饰器是密切相关的:
闭包和装饰器是 Python 中最具表现力和灵活性的特性之一,掌握它们能帮助你写出更加简洁、高效、可维护的代码。通过本文的学习,你已经掌握了闭包和装饰器的原理、实现方法和调用方式,并结合自带装饰器和实战示例进行了深入的探索。
在实际开发中,要遵循装饰器的最佳实践,合理安排装饰器的顺序,保留原函数的元信息,避免过度装饰,使用参数化装饰器提高灵活性。同时,要注意装饰器的性能问题,对于频繁调用的函数,使用缓存装饰器来提高运行效率。