Python基础-11.装饰器

TOC

一、装饰器基础

装饰器(decorators)是Python中的一种高级功能,它允许你动态地修改函数或类的行为。

装饰器的应用场景:

  • 日志记录: 装饰器可用于记录函数的调用信息、参数和返回值。
  • 性能分析: 可以使用装饰器来测量函数的执行时间。
  • 权限控制: 装饰器可用于限制对某些函数的访问权限。
  • 缓存: 装饰器可用于实现函数结果的缓存,以提高性能。

基本语法

所谓的装饰器也就是一个函数,装饰器的意思就如字面意思一样,就是将一个函数进行装饰,添加一些特别的功能。
【示例】一个简单的装饰器使用代码段

# 构建一个装饰器函数
def demo(func):
    def wrapper(*arg, **kwargs):
        # 调用原始函数之前的代码功能
        print("before call")
        result = func(*arg, **kwargs)
        # 调用原始函数之后的代码功能
        print("after call")
        return result
    return wrapper

# 使用装饰器
@demo
def greet(name):
    print("hello", name)  # 原始函数的功能实现

greet('Jack')  # 运行函数

解析:
1.上面创建了一个装饰器函数demo,他接收一个叫func类型为函数的参数,并返回一个内部函数wrapper
2.内部函数wrapper在调用传入的函数前后新增了额外的功能行为,最后返回传入函数的值。
3.创建一个函数greet,在定义greet函数前面使用@demo,Python自动将greet作为参数传递给demo,然后返回的wrapper函数替换掉原来的demo

总结:装饰器就是将一个简单的函数传入装饰器函数中,将函数重写,返回一个最终功能更完善的函数。

使用装饰器

装饰器通过@符号应用在函数定义之前。
使用装饰器,如下所示:

@decorator
def hello():
    print("hello")

上面的代码就相当于以下代码:

# 定义hello函数
def hello():
    print("hello")

# 将hello对象传入decorator装饰器,返回一个新函数重新赋值hello
hello = decorator(hello)

相当于将hello函数传递给decorator装饰器,将函数重写成完善的函数后返回赋值给hello,之后每次调用hello函数时,调用的是经过装饰器处理后的函数。
【示例】上面功能完整的装饰器示例,在执行函数前后增加了打印信息

# decorator装饰器函数
def decorator(func):
    def wrapper():
        print("before call")  # 执行函数前打印信息
        func()  # 调用原始函数
        print("after call")   # 执行函数后打印信息
    return wrapper  # 返回新的函数

# 使用decorator装饰器传入hello函数
@decorator
def hello():
    print("hello")

hello()  # 调用新的hello函数
''' 运行结果如下
before call
hello
after call
'''

带参数的装饰器

1.原函数传递参数

如果原函数需要参数,可以在装饰器的wrapper函数中传递参数,因为返回的是wrapper对象,所以wrapper也要加入参数选项,func对象调用也要加入参数选项。
【示例】代码如下:

def decorator(func):
    def wrapper(*arg, **kwargs):
        print("before call")
        result = func(*arg, **kwargs)
        print("after call")
        return result
    return wrapper

# 使用decorator装饰器
@decorator
def hello(name):
    print("hello", name)


hello('Jack')
''' 运行结果如下
before call
hello Jack
after call
'''

2.装饰器传递参数

装饰器本身也可以接受参数,装饰器传递参数的意思就是在使用装饰器时装饰器后面传入参数,如:@log("INFO"),我们只需要额外定义一个外层函数来接收函数即可。
【示例】代码如下:

def log(prefix):  # 外层接收参数
    def decorator(func):  # 中层接收被装饰的函数
        def wrapper(*args, **kwargs):  # 内层执行逻辑
            print(f'[{prefix}] — 函数开始执行...')
            result = func(*args, **kwargs)
            print('运行结果为:', result)
            print(f"[{prefix}] — 函数执行结束...")
        return wrapper
    return decorator

# 使用装饰器并传递参数"INFO"
@log("INFO")
def add(a, b):
    return a + b

# 使用装饰器并传递参数"DEBUG"
@log("DEBUG")
def multiply(a, b):
    return a * b

# 调用装饰之后的函数
add(3, 4)
multiply(2, 5)
''' 运行结果如下
[INFO] — 函数开始执行...
运行结果为: 7
[INFO] — 函数执行结束...
[DEBUG] — 函数开始执行...
运行结果为: 10
[DEBUG] — 函数执行结束...
'''

二、装饰器进阶知识

类装饰器

除了可以创建函数装饰器,类也可以使用装饰器。类装饰器(Class Decorator)是一种用于动态修改类行为的装饰器,它接收一个类作为参数,并返回一个新的类或修改后的类。

类装饰器的作用:

  • 添加/修改类的方法或属性
  • 拦截实例化过程
  • 实现单例模式、日志记录、权限检查等功能

1.函数形式

函数形式的类装饰器就是将装饰器写成一个函数,接收一个 类作为参数,然后返回修改后的类。
【示例】代码如下:

def decorator(cls):
    class Wrapper:
        def __init__(self, *args, **kwargs):
            # 实例化原始类
            self.wrapped = cls(*args, **kwargs)

        def __getattr__(self, name):
            # 拦截未定义的属性访问,转发给原始类
            return getattr(self.wrapped, name)

        def demo(self):
            print(f"调用 {cls.__name__}.display()前")
            self.wrapped.demo()
            print(f"调用 {cls.__name__}.display()后")

    return Wrapper

# 使用decorator装饰器,构建新的类
@decorator
class OurClass:
    def demo(self):
        print('OurClass类传入的新demo函数')

OurClass().demo()  # 调用使用装饰器之后的类里的函数
''' 运行结果如下
调用 OurClass.display()前
OurClass类传入的新demo函数
调用 OurClass.display()后
'''

2.类形式

类形式的类装饰器,就是用一个 类来实现装饰器(本质上要让类实例可调用,所以要实现__call__方法)。
【示例】代码如下:

class MyDecorator:
    def __init__(self, cls):
        print("初始化装饰器")
        self._cls = cls   # 保存被装饰的类

    def __call__(self, *args, **kwargs):
        print("调用装饰器,创建实例")
        return self._cls(*args, **kwargs)

# 使用MyDecorator装饰器
@MyDecorator
class Person:
    def __init__(self, name):
        self.name = name

p = Person("Jack")
print(p.name)
'''运行结果如下
初始化装饰器
调用装饰器,创建实例
Jack
'''

内置装饰器

就跟内置函数一样,Python也有很多内置装饰器。

内置装饰器列表:

  • @staticmethod:定义静态方法,方法不依赖实例 (self) 或类 (cls),可用于类里有一些工具函数,但逻辑上属于类,就可以用静态方法。
  • @classmethod:定义 类方法,第一个参数是cls(类本身),可用于做“工厂方法”,用类本身创建对象。
  • @property:把一个方法变成只读属性,可用于给用户暴露“像属性一样”的接口,但内部可能是方法计算。

【示例】创建一个类使用以上三个内置装饰器

class Demo:
    population = 0  # 类变量

    def __init__(self, name, age):
        self._name = name
        self._age = age
        Demo.population += 1

    # ----------- property 示例 -----------
    @property
    def name(self):
        # 把方法当成属性使用
        return self._name

    # ----------- classmethod 示例 -----------
    @classmethod
    def how_many(cls):
        # 返回当前人口数
        return cls.population

    # ----------- staticmethod 示例 -----------
    @staticmethod
    def is_adult(age):
        # 判断是否成年
        return age >= 18

# ---------------- 测试 ----------------
p1 = Demo("Jack", 20)
p2 = Demo("Tom", 15)
print(p1.name)            # 使用 @property
print(Demo.how_many())    # 使用 @classmethod
print(Demo.is_adult(17))  # 使用 @staticmethod
''' 运行结果如下
Jack
2
False
'''

多个装饰器堆叠

可以将多个装饰器堆叠在一起,它们会按照从下到上的顺序依次应用。
【示例】代码如下:

# 第一个装饰器函数
def decorator1(func):
    def wrapper(*args, **kwargs):
        print("进入 decorator1")
        result = func(*args, **kwargs)
        print("退出 decorator1")
        return result
    return wrapper

#第二个装饰器函数
def decorator2(func):
    def wrapper(*args, **kwargs):
        print("进入 decorator2")
        result = func(*args, **kwargs)
        print("退出 decorator2")
        return result
    return wrapper

# 使用两个装饰器
@decorator1
@decorator2
def say_hello(name):
    print(f"Hello {name}")

say_hello("Jack")
''' 运行结果如下
进入 decorator1
进入 decorator2
Hello Jack
退出 decorator2
退出 decorator1
'''