通过动手示例学习 Python 装饰器。深入理解闭包、函数式与类式装饰器,并编写可复用、优雅的代码。
装饰器是 Python 中一种强大而优雅的特性,它允许你在不修改原有函数代码的前提下,修改或扩展函数和方法的行为。
装饰器是一种设计模式,允许用户在不改变对象结构的情况下为其添加新功能。装饰器通常应用于函数,在增强或修改函数行为方面起着关键作用。传统上,装饰器被放置在要装饰的函数定义之前。本教程将演示如何在 Python 函数中有效使用装饰器。
函数是一等公民(First-Class Objects)
在 Python 中,函数是一等公民。这意味着它们支持以下操作:
- 作为参数传递
- 从函数中返回
- 被修改
- 被赋值给变量
这一特性至关重要,因为它使函数可以像其他对象一样被灵活处理。
将函数赋值给变量
我们首先创建一个函数,每次调用时将数字加一。然后将该函数赋值给一个变量,并通过该变量调用函数。
def plus_one(number):
return number + 1
add_one = plus_one
add_one(5) # 输出: 6
在函数内部定义函数
Python 允许在函数内部定义函数,这是构建装饰器的核心特性之一。
def plus_one(number):
def add_one(number):
return number + 1
result = add_one(number)
return result
plus_one(4) # 输出: 5
将函数作为参数传递
函数也可以作为参数传递给其他函数:
def plus_one(number):
return number + 1
def function_call(function):
number_to_add = 5
return function(number_to_add)
function_call(plus_one) # 输出: 6
函数返回另一个函数
函数还可以生成并返回另一个函数:
def hello_function():
def say_hi():
return "Hi"
return say_hi
hello = hello_function()
hello() # 输出: 'Hi'
内部函数与闭包(Closures)
Python 允许嵌套函数访问其外层函数的作用域。这一概念称为闭包(Closure),对理解装饰器至关重要。
闭包:一个函数“记住”了它被创建时的环境,即使该环境已不再活跃。
闭包示例
def outer_function(message):
def inner_function():
print(f"Message from closure: {message}")
return inner_function
closure_function = outer_function("Hello, closures!")
closure_function()
# 输出: Message from closure: Hello, closures!
在此示例中:
inner_function是一个闭包,因为它访问了外层作用域中的变量message。- 即使
outer_function已执行完毕,inner_function仍能访问message。
在装饰器中,包装函数(wrapper)就是一个闭包,它会记住被装饰的函数以及装饰器中定义的任何状态。
def simple_decorator(func):
def wrapper():
print("Before the function call")
func()
print("After the function call")
return wrapper
@simple_decorator
def greet():
print("Hello!")
greet()
# 输出:
# Before the function call
# Hello!
# After the function call
创建你的第一个装饰器
现在你已经理解了闭包,我们可以创建第一个真正的装饰器了。
下面是一个将句子转为大写的装饰器:
def uppercase_decorator(function):
def wrapper():
func = function()
make_uppercase = func.upper()
return make_uppercase
return wrapper
手动应用装饰器
def say_hi():
return 'hello there'
decorate = uppercase_decorator(say_hi)
decorate() # 输出: 'HELLO THERE'
使用 @ 语法(推荐)
Python 提供了更简洁的语法——使用 @ 符号:
@uppercase_decorator
def say_hi():
return 'hello there'
say_hi() # 输出: 'HELLO THERE'
堆叠多个装饰器
你可以将多个装饰器叠加到同一个函数上,顺序很重要!
import functools
def split_string(function):
@functools.wraps(function)
def wrapper():
func = function()
splitted_string = func.split()
return splitted_string
return wrapper
@split_string
@uppercase_decorator
def say_hi():
return 'hello there'
say_hi() # 输出: ['HELLO', 'THERE']
注意:装饰器从下往上应用。如果顺序颠倒(先
split_string后uppercase_decorator),会出错,因为列表没有.upper()方法。
✅ 最佳实践:使用
functools.wraps保留原始函数的元数据(如__name__、__doc__),便于调试。
装饰器接受函数参数
装饰器也可以处理带参数的函数:
def decorator_with_arguments(function):
def wrapper_accepting_arguments(arg1, arg2):
print("My arguments are: {0}, {1}".format(arg1, arg2))
function(arg1, arg2)
return wrapper_accepting_arguments
@decorator_with_arguments
def cities(city_one, city_two):
print("Cities I love are {0} and {1}".format(city_one, city_two))
cities("Nairobi", "Accra")
# 输出:
# My arguments are: Nairobi, Accra
# Cities I love are Nairobi and Accra
⚠️ 注意:装饰器内部的
wrapper参数必须与被装饰函数的参数匹配。
通用装饰器:使用 *args 和 **kwargs
为了适用于任意函数,使用 *args 和 **kwargs:
def a_decorator_passing_arbitrary_arguments(function_to_decorate):
def a_wrapper_accepting_arbitrary_arguments(*args, **kwargs):
print('The positional arguments are', args)
print('The keyword arguments are', kwargs)
function_to_decorate(*args, **kwargs) # 修复:应传递 **kwargs
return a_wrapper_accepting_arbitrary_arguments
示例
@a_decorator_passing_arbitrary_arguments
def function_with_no_argument():
print("No arguments here.")
function_with_no_argument()
# 输出:
# The positional arguments are ()
# The keyword arguments are {}
# No arguments here.
@a_decorator_passing_arbitrary_arguments
def function_with_arguments(a, b, c):
print(a, b, c)
function_with_arguments(1, 2, 3)
# 输出:
# The positional arguments are (1, 2, 3)
# The keyword arguments are {}
# 1 2 3
@a_decorator_passing_arbitrary_arguments
def function_with_keyword_arguments(first_name, last_name):
print(f"Hello {first_name} {last_name}")
function_with_keyword_arguments(first_name="Derrick", last_name="Mwiti")
# 输出:
# The positional arguments are ()
# The keyword arguments are {'first_name': 'Derrick', 'last_name': 'Mwiti'}
# Hello Derrick Mwiti
向装饰器本身传递参数
有时你需要配置装饰器本身(如设置日志级别、缓存大小等)。这时需要装饰器工厂:
def decorator_maker_with_arguments(decorator_arg1, decorator_arg2, decorator_arg3):
def decorator(func):
def wrapper(function_arg1, function_arg2, function_arg3):
print("The wrapper can access all the variables\n"
"\t- from the decorator maker: {0} {1} {2}\n"
"\t- from the function call: {3} {4} {5}\n"
"and pass them to the decorated function"
.format(decorator_arg1, decorator_arg2, decorator_arg3,
function_arg1, function_arg2, function_arg3))
return func(function_arg1, function_arg2, function_arg3)
return wrapper
return decorator
pandas = "Pandas"
@decorator_maker_with_arguments(pandas, "Numpy", "Scikit-learn")
def decorated_function_with_arguments(function_arg1, function_arg2, function_arg3):
print("This is the decorated function and it only knows about its arguments: {0} {1} {2}"
.format(function_arg1, function_arg2, function_arg3))
decorated_function_with_arguments(pandas, "Science", "Tools")
输出:
The wrapper can access all the variables
- from the decorator maker: Pandas Numpy Scikit-learn
- from the function call: Pandas Science Tools
and pass them to the decorated function
This is the decorated function and it only knows about its arguments: Pandas Science Tools
调试装饰器:保留元数据
装饰器会“隐藏”原始函数的元信息:
decorated_function_with_arguments.__name__ # 输出: 'wrapper'
decorated_function_with_arguments.__doc__ # 输出: 'This is the wrapper function'
解决方案:使用 functools.wraps
import functools
def uppercase_decorator(func):
@functools.wraps(func)
def wrapper():
return func().upper()
return wrapper
@uppercase_decorator
def say_hi():
"This will say hi"
return 'hello there'
say_hi() # 'HELLO THERE'
say_hi.__name__ # 'say_hi'
say_hi.__doc__ # 'This will say hi'
✅ 强烈建议:始终在自定义装饰器中使用 @functools.wraps(func)。
类式装饰器(Class-Based Decorators)
除了函数式装饰器,Python 还支持类式装饰器,通过实现 __call__ 方法使其可调用。
基本示例
class UppercaseDecorator:
def __init__(self, function):
self.function = function
def __call__(self, *args, **kwargs):
result = self.function(*args, **kwargs)
return result.upper()
@UppercaseDecorator
def greet():
return "hello there"
print(greet()) # 输出: HELLO THERE
工作原理
__init__:接收被装饰的函数。__call__:当调用被装饰函数时触发,可修改其行为。
优势
- 有状态(Stateful):可通过实例变量保存状态。
- 可读性强:复杂逻辑封装在类中更清晰。
有状态装饰器示例:调用计数器
class CallCounter:
def __init__(self, function):
self.function = function
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"Function {self.function.__name__} has been called {self.count} times.")
return self.function(*args, **kwargs)
@CallCounter
def say_hello():
print("Hello!")
say_hello()
say_hello()
# 输出:
# Function say_hello has been called 1 times.
# Hello!
# Function say_hello has been called 2 times.
# Hello!
真实场景:缓存(Caching)
Python 内置的 @lru_cache 是一个经典装饰器,用于缓存昂贵函数的计算结果:
from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(50)) # 第二次调用极快
装饰器的常见用途
- 日志记录:跟踪函数调用、参数和返回值
- 身份验证:在 Flask/Django 中控制访问权限
- 执行时间测量:性能分析
- 重试机制:网络请求失败时自动重试
- 输入验证:在函数执行前校验参数
总结
装饰器让你能在不修改源码的情况下动态改变函数、方法或类的行为。它遵循 DRY(Don't Repeat Yourself) 原则,广泛应用于:
- Web 框架中的权限控制(Flask、Django)
- 日志系统
- 性能监控
- 同步控制
✅ 最佳实践回顾:
- 使用
@functools.wraps保留元数据 - 通用装饰器使用
*args, **kwargs - 复杂逻辑考虑类式装饰器
- 多装饰器注意堆叠顺序
- 谨慎处理异常和返回值
掌握装饰器,你将写出更优雅、更 Pythonic 的代码!