Python装饰器用法详解
1. 引言
Python装饰器是一种强大而优雅的编程工具,它允许在不修改原函数代码的情况下,为函数添加额外的功能。装饰器本质上是一个可调用对象(通常是函数),它接受一个函数作为参数,并返回一个新的函数。这种设计模式广泛应用于日志记录、性能测试、事务处理、缓存、权限验证等场景。掌握装饰器是Python进阶编程的关键技能之一。
2. 理解装饰器的基础:函数也是对象
在深入装饰器之前,我们需要理解Python中函数的一个重要特性:函数是一等公民。这意味着函数可以像其他对象一样被传递、赋值给变量、作为参数传递给其他函数,以及从其他函数返回。
def greet(name):
return f"Hello, {name}!"
# 函数作为对象赋值
welcome = greet
print(welcome("Alice")) # 输出: Hello, Alice!
# 函数作为参数传递
def call_function(func, arg):
return func(arg)
print(call_function(greet, "Bob")) # 输出: Hello, Bob!
3. 装饰器的本质:闭包与嵌套函数
装饰器通常使用闭包来实现。闭包是一个函数对象,它记住了外部作用域中的变量。让我们通过一个简单的例子来理解装饰器的工作原理:
def simple_decorator(func):
def wrapper():
print("Something before the function")
func()
print("Something after the function")
return wrapper
def say_hello():
print("Hello!")
# 使用装饰器
say_hello = simple_decorator(say_hello)
say_hello()
输出:
Something before the function
Hello!
Something after the function
在这个例子中:
simple_decorator 是一个装饰器函数,它接受一个函数 func 作为参数
在装饰器内部,我们定义了一个新的函数 wrapper,它在调用原函数前后添加了额外的行为
装饰器返回这个 wrapper 函数
最后我们将 say_hello 变量指向返回的 wrapper 函数
4. @语法糖的使用
Python提供了 @ 语法糖来简化装饰器的使用,使代码更加简洁:
def simple_decorator(func):
def wrapper():
print("Before")
func()
print("After")
return wrapper
@simple_decorator
def say_hello():
print("Hello!")
say_hello()
输出:
Before
Hello!
After
@simple_decorator 等价于 say_hello = simple_decorator(say_hello)
5. 带参数的装饰器
有时我们需要创建可以接受参数的装饰器。这需要三层嵌套函数:最外层接收装饰器参数,中间层接收被装饰函数,最内层是实际执行的逻辑。
def repeat(num_times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(num_times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(num_times=3)
def greet(name):
print(f"Hello, {name}!")
greet("Alice")
输出:
Hello, Alice!
Hello, Alice!
Hello, Alice!
在这个例子中:
repeat 是一个工厂函数,它接受 num_times 参数并返回一个装饰器
decorator 是实际的装饰器,它接收 func 函数
wrapper 是最终执行的函数,它调用 func 函数 num_times 次
6. 保留原函数的元信息
当我们使用装饰器时,原函数的元信息(如函数名、文档字符串等)会被 wrapper 函数覆盖。为了解决这个问题,我们可以使用 functools.wraps 装饰器:
from functools import wraps
def log_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with args: {args}, kwargs: {kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} returned: {result}")
return result
return wrapper
@log_decorator
def add(a, b):
"""Add two numbers"""
return a + b
print(add(3, 5))
print(add.__name__)
print(add.__doc__)
输出:
Calling add with args: (3, 5), kwargs: {}
add returned: 8
8
add
Add two numbers
functools.wraps 帮助保留了原函数的名称、文档字符串等元信息,这对于调试和文档生成非常重要。
7. 装饰器链(多个装饰器)
我们可以为一个函数应用多个装饰器,装饰器的应用顺序是从下到上(从靠近函数的装饰器开始):
def bold(func):
@wraps(func)
def wrapper(*args, **kwargs):
return f"<b>{func(*args, **kwargs)}</b>"
return wrapper
def italic(func):
@wraps(func)
def wrapper(*args, **kwargs):
return f"<i>{func(*args, **kwargs)}</i>"
return wrapper
@bold
@italic
def greet(name):
return f"Hello, {name}!"
print(greet("Alice")) # 输出: <b><i>Hello, Alice!</i></b>
执行顺序相当于:greet = bold(italic(greet)),即先应用 italic 装饰器,再应用 bold 装饰器。
8. 类作为装饰器
除了函数,我们也可以使用类作为装饰器。类需要实现 __call__ 方法来使其可调用:
class CountCalls:
def __init__(self, func):
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"Call {self.count} of {self.func.__name__}")
return self.func(*args, **kwargs)
@CountCalls
def greet(name):
return f"Hello, {name}!"
print(greet("Alice"))
print(greet("Bob"))
输出:
Call 1 of greet
Hello, Alice!
Call 2 of greet
Hello, Bob!
类装饰器通常用于需要维护状态或复杂逻辑的场景。
9. 实际应用场景:计时装饰器
计时装饰器是装饰器的一个经典应用,用于测量函数的执行时间:
import time
from functools import wraps
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start_time = time.perf_counter()
result = func(*args, **kwargs)
end_time = time.perf_counter()
run_time = end_time - start_time
print(f"Finished {func.__name__!r} in {run_time:.4f} secs")
return result
return wrapper
@timer
def sum_numbers(n):
"""Sum numbers from 1 to n"""
return sum(range(1, n+1))
print(sum_numbers(1000000))
输出:
Finished 'sum_numbers' in 0.0432 secs
500000500000
10. 实际应用场景:缓存装饰器
缓存装饰器可以避免重复计算,显著提高递归或计算密集型函数的性能:
from functools import wraps
def cache(func):
memo = {}
@wraps(func)
def wrapper(*args):
if args not in memo:
memo[args] = func(*args)
return memo[args]
return wrapper
@cache
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(35)) # 第一次计算需要时间
print(fibonacci(35)) # 第二次直接从缓存返回,非常快
11. 实际应用场景:权限验证装饰器
在Web开发中,装饰器常用于权限验证:
from functools import wraps
def permission_required(permission):
def decorator(func):
@wraps(func)
def wrapper(user, *args, **kwargs):
if permission not in user.permissions:
raise PermissionError(f"User {user.name} lacks {permission} permission")
return func(user, *args, **kwargs)
return wrapper
return decorator
class User:
def __init__(self, name, permissions):
self.name = name
self.permissions = permissions
@permission_required("admin")
def delete_user(current_user, user_to_delete):
print(f"User {current_user.name} deleted {user_to_delete.name}")
admin = User("Admin", ["admin", "editor"])
regular_user = User("Regular", ["editor"])
# 这将正常执行
delete_user(admin, regular_user)
# 这将抛出PermissionError
delete_user(regular_user, admin)
12. 装饰器参数验证
我们可以创建装饰器来验证函数参数:
from functools import wraps
def validate_types(**type_hints):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
# 将参数名与值对应起来
bound_args = {}
# 获取函数的参数名
arg_names = func.__code__.co_varnames[:func.__code__.co_argcount]
# 处理位置参数
for i, arg in enumerate(args):
if i < len(arg_names):
bound_args[arg_names[i]] = arg
# 处理关键字参数
bound_args.update(kwargs)
# 验证类型
for param_name, value in bound_args.items():
if param_name in type_hints:
expected_type = type_hints[param_name]
if not isinstance(value, expected_type):
raise TypeError(
f"Argument '{param_name}' must be {expected_type}, "
f"got {type(value).__name__}"
)
return func(*args, **kwargs)
return wrapper
return decorator
@validate_types(name=str, age=int, hobbies=list)
def create_user(name, age, hobbies=[]):
return f"User {name} ({age}) with hobbies: {', '.join(hobbies)}"
print(create_user("Alice", 30, ["reading", "coding"])) # 正常
# create_user("Bob", "thirty", []) # 这会抛出TypeError: Argument 'age' must be <class 'int'>, got str
13. 装饰器的陷阱与注意事项
装饰器执行时机:装饰器在模块导入时立即执行,而不是在函数调用时。这意味着装饰器中的任何副作用都会在导入时发生。
装饰后的函数身份:装饰器会替换原函数,如果代码依赖函数的 __name__ 或其他属性,应使用 functools.wraps 保留这些属性。
装饰器顺序:多个装饰器时,顺序很重要。@decorator1 @decorator2 func 等价于 func = decorator1(decorator2(func))。
带参数装饰器:带参数的装饰器需要额外的函数层来接收参数。
类方法装饰:装饰类方法时,self 参数会被自动传递,但装饰器本身需要正确处理。
from functools import wraps
def method_decorator(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
print(f"Calling {func.__name__} on instance {self}")
return func(self, *args, **kwargs)
return wrapper
class MyClass:
@method_decorator
def my_method(self, value):
return value * 2
obj = MyClass()
print(obj.my_method(5)) # 输出: Calling my_method on instance <__main__.MyClass object> 10
14. 高级装饰器技术
14.1. 装饰器类与状态保持
当装饰器需要保持状态时,使用类作为装饰器是更好的选择:
class CountCalls:
def __init__(self, func):
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"Call {self.count} of {self.func.__name__}")
return self.func(*args, **kwargs)
@CountCalls
def greet(name):
return f"Hello, {name}!"
print(greet("Alice"))
print(greet("Bob"))
print(greet.count) # 访问装饰器状态
14.2. 可选参数的装饰器
创建可以带参数或不带参数的装饰器:
from functools import wraps
def optional_debug(func=None, *, prefix="DEBUG"):
if func is None:
# 被调用时带参数,返回装饰器
return lambda f: optional_debug(f, prefix=prefix)
@wraps(func)
def wrapper(*args, **kwargs):
print(f"{prefix}: {func.__name__} called")
return func(*args, **kwargs)
return wrapper
# 不带参数使用
@optional_debug
def add(a, b):
return a + b
# 带参数使用
@optional_debug(prefix="INFO")
def multiply(a, b):
return a * b
print(add(2, 3))
print(multiply(2, 3))
15. 总结
Python装饰器是一种强大而灵活的代码增强机制,它允许我们以非侵入式的方式为函数添加额外功能。通过深入理解装饰器的工作原理——函数作为对象、闭包和嵌套函数——我们可以创建各种实用的装饰器来解决实际问题。
关键要点包括:
装饰器本质上是一个接收函数并返回新函数的可调用对象
使用 @ 语法糖可以简化装饰器的应用
带参数的装饰器需要三层嵌套函数结构
使用 functools.wraps 可以保留原函数的元信息
装饰器可以叠加使用,顺序从下到上
类装饰器适合需要保持状态的场景
实际应用包括日志记录、性能测试、缓存、权限验证等
掌握装饰器不仅能帮助我们编写更简洁、可维护的代码,还能让我们更好地理解Python的高级特性。随着经验的积累,你会发现装饰器是解决许多编程难题的优雅方案。







