各位听众,大家好!今天咱们来聊聊Python里一个既神秘又实用的小玩意儿——Decorator,也就是装饰器。别害怕“设计模式”这四个字,听我慢慢道来,保证你听完觉得,这玩意儿,真香!
开场白:生活中的“装饰”
话说回来,啥叫装饰器?咱先从生活中找找感觉。你想啊,你穿一件普通的白T恤,挺朴素的吧?但要是你往上面印个喜欢的图案,或者戴条项链,甚至穿个外套,是不是感觉立刻就不一样了?这不就是给T恤“装饰”了一下嘛!
Python里的装饰器,作用也差不多,就是给函数或者类“装饰”一下,给它添点新功能,但又不改变它原本的代码。
第一幕:函数“变身”大法
最简单的装饰器,其实就是个函数。这个函数接收另一个函数作为参数,然后返回一个“增强版”的函数。
def say_hello(name):
return f"Hello, {name}!"
def make_uppercase(func):
def wrapper(name):
original_result = func(name)
modified_result = original_result.upper()
return modified_result
return wrapper
uppercase_hello = make_uppercase(say_hello) # 把 say_hello 传给 make_uppercase,得到一个新函数
print(uppercase_hello("Alice")) # 输出:HELLO, ALICE!
print(say_hello("Alice")) # 输出:Hello, Alice!
上面的代码里,make_uppercase
就是一个装饰器。它接收say_hello
函数,然后在wrapper
函数里,把say_hello
的返回值变成了大写。最后,make_uppercase
返回了wrapper
函数。
注意这里,wrapper
函数非常关键,它负责接收原始函数的参数,调用原始函数,对原始函数的返回值进行修改,然后返回修改后的返回值。
第二幕:@
语法糖登场
每次都这么写 uppercase_hello = make_uppercase(say_hello)
,是不是感觉有点麻烦?Python 大神们当然也觉得麻烦,所以他们发明了一个“语法糖”——@
符号。
有了 @
符号,上面的代码可以写成这样:
def make_uppercase(func):
def wrapper(name):
original_result = func(name)
modified_result = original_result.upper()
return modified_result
return wrapper
@make_uppercase
def say_hello(name):
return f"Hello, {name}!"
print(say_hello("Alice")) # 输出:HELLO, ALICE!
是不是简洁多了?@make_uppercase
相当于 say_hello = make_uppercase(say_hello)
。这就是语法糖的魅力,让代码更优雅。
第三幕:带参数的装饰器
有时候,我们希望装饰器本身也能接收参数,来控制装饰行为。比如,我们想让 make_uppercase
装饰器可以选择是否将字符串转换为大写。
def make_uppercase(uppercase=True): # 装饰器工厂函数
def decorator(func): # 真正的装饰器
def wrapper(name):
original_result = func(name)
if uppercase:
modified_result = original_result.upper()
else:
modified_result = original_result
return modified_result
return wrapper
return decorator
@make_uppercase(uppercase=False) # 调用装饰器工厂函数,返回装饰器
def say_hello(name):
return f"Hello, {name}!"
print(say_hello("Alice")) # 输出:Hello, Alice!
@make_uppercase(uppercase=True)
def say_hello_2(name):
return f"Hello, {name}!"
print(say_hello_2("Bob")) # 输出:HELLO, BOB!
这里,make_uppercase
本身是一个函数,它接收一个参数 uppercase
,然后返回一个真正的装饰器 decorator
。decorator
接收函数作为参数,并返回 wrapper
函数。
注意,带参数的装饰器实际上是一个装饰器工厂,它返回一个装饰器。
第四幕:通用装饰器
上面的例子只能装饰接收一个参数的函数。如果我们要装饰接收任意参数的函数,该怎么办呢?*args
和 **kwargs
就派上用场了。
def my_decorator(func):
def wrapper(*args, **kwargs):
print("Before calling the function.")
result = func(*args, **kwargs)
print("After calling the function.")
return result
return wrapper
@my_decorator
def add(x, y):
return x + y
@my_decorator
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
print(add(5, 3))
print(greet("Charlie"))
print(greet("David", greeting="Good morning"))
*args
接收任意数量的位置参数,**kwargs
接收任意数量的关键字参数。这样,wrapper
函数就可以接收任何函数的参数了。
第五幕:装饰器与类
装饰器不仅可以装饰函数,还可以装饰类。
def singleton(cls):
instances = {}
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class DatabaseConnection:
def __init__(self, host, port):
self.host = host
self.port = port
def connect(self):
print(f"Connecting to {self.host}:{self.port}")
db1 = DatabaseConnection("localhost", 5432)
db2 = DatabaseConnection("localhost", 5432)
print(db1 is db2) # True。说明db1和db2是同一个实例
db1.connect() # 连接数据库
上面的代码里,singleton
装饰器把 DatabaseConnection
类变成了一个单例类。也就是说,无论创建多少个 DatabaseConnection
对象,它们实际上都是同一个对象。
第六幕:使用 functools.wraps
使用装饰器后,原始函数的 __name__
和 __doc__
属性会发生改变,这可能会影响代码的调试和文档生成。为了解决这个问题,可以使用 functools.wraps
。
import functools
def my_decorator(func):
@functools.wraps(func) # 保持原始函数的元数据
def wrapper(*args, **kwargs):
"""Wrapper function's docstring."""
print("Before calling the function.")
result = func(*args, **kwargs)
print("After calling the function.")
return result
return wrapper
@my_decorator
def my_function(x, y):
"""Original function's docstring."""
return x + y
print(my_function.__name__) # 输出:my_function
print(my_function.__doc__) # 输出:Original function's docstring.
functools.wraps(func)
会把原始函数的 __name__
、__doc__
等属性复制到 wrapper
函数。
第七幕:装饰器的实际应用
装饰器在实际开发中有很多用途,比如:
- 日志记录: 记录函数的调用信息。
- 性能测试: 测量函数的执行时间。
- 权限验证: 检查用户是否有权限访问某个函数。
- 缓存: 缓存函数的返回值,避免重复计算。
- 重试机制: 在函数执行失败时自动重试。
- 参数校验: 验证函数参数的类型和取值范围。
下面是一些例子:
1. 计时器
import time
def timer(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
execution_time = end_time - start_time
print(f"{func.__name__} took {execution_time:.4f} seconds to execute.")
return result
return wrapper
@timer
def my_function(n):
sum = 0
for i in range(n):
sum += i
return sum
my_function(1000000)
2. 权限验证
def requires_permission(permission):
def decorator(func):
def wrapper(*args, **kwargs):
user = kwargs.get('user') # 假设参数中有一个 user 关键字
if user and user.has_permission(permission): # 假设user对象有 has_permission方法
return func(*args, **kwargs)
else:
return "Permission denied."
return wrapper
return decorator
class User:
def __init__(self, permissions):
self.permissions = permissions
def has_permission(self, permission):
return permission in self.permissions
@requires_permission("admin")
def delete_user(user_id, user=None):
return f"User {user_id} deleted."
admin_user = User(["admin", "edit"])
normal_user = User(["edit"])
print(delete_user(123, user=admin_user))
print(delete_user(456, user=normal_user))
3. 缓存
import functools
def cache(func):
cached_results = {}
@functools.wraps(func)
def wrapper(*args):
if args in cached_results:
return cached_results[args]
else:
result = func(*args)
cached_results[args] = result
return result
return wrapper
@cache
def fibonacci(n):
if n <= 1:
return n
else:
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(10))
print(fibonacci(10)) # 第二次调用直接从缓存中获取结果
第八幕:总结与展望
装饰器是 Python 中一个非常强大的特性,它可以让你在不修改原有代码的情况下,给函数或类添加额外的功能。通过学习装饰器,你可以写出更简洁、更优雅、更易于维护的代码。
要点回顾:
特性 | 描述 | 示例 |
---|---|---|
基本装饰器 | 接收函数作为参数,返回一个“增强版”的函数。 | def my_decorator(func): ... |
@ 语法糖 |
简化装饰器的调用。 | @my_decorator |
带参数的装饰器 | 允许装饰器接收参数,更灵活地控制装饰行为。 | def my_decorator(arg1, arg2): ... |
通用装饰器 | 可以装饰接收任意参数的函数。 | def my_decorator(func): def wrapper(*args, **kwargs): ... |
类装饰器 | 可以装饰类,例如实现单例模式。 | @singleton |
functools.wraps |
保持原始函数的元数据,避免 __name__ 和 __doc__ 属性丢失。 |
@functools.wraps(func) |
实际应用 | 日志记录、性能测试、权限验证、缓存、重试机制等。 | python @timer def my_function(n): ... |
希望今天的分享能让你对 Python 装饰器有一个更深入的了解。记住,编程就像做菜,掌握了各种“调味料”(也就是各种技术),才能做出美味佳肴! 以后遇到需要扩展函数功能的场景,不妨试试装饰器,你会发现,它真的很好用!咱们下次再见!