Python高级技术之:`Python`的`Decorator`模式:从`Python`语法糖到设计模式的演变。

各位听众,大家好!今天咱们来聊聊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,然后返回一个真正的装饰器 decoratordecorator 接收函数作为参数,并返回 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 装饰器有一个更深入的了解。记住,编程就像做菜,掌握了各种“调味料”(也就是各种技术),才能做出美味佳肴! 以后遇到需要扩展函数功能的场景,不妨试试装饰器,你会发现,它真的很好用!咱们下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注