装饰器

245 阅读5分钟

装饰器

原理

在不修改原功能函数的基础上而扩展新的功能

目标
  1. 怎么写、使用装饰器?
  2. 什么时候需要使用装饰器?
装饰器类

通常为装饰器函数,但是装饰器函数内一般只能有1个包装函数,因此,如果想在装饰器内调用另外的函数,需要使用装饰器类

from functools import wraps
 
class logit(object):
    def __init__(self, logfile='out.log'):
        self.logfile = logfile
 
    def __call__(self, func):
        @wraps(func)
        def wrapped_function(*args, **kwargs):
            log_string = func.__name__ + " was called"
            print(log_string)
            # 打开logfile并写入
            with open(self.logfile, 'a') as opened_file:
                # 现在将日志打到指定的文件
                opened_file.write(log_string + '\n')
            # 现在,发送一个通知
            self.notify()
            return func(*args, **kwargs)
        return wrapped_function
 
    def notify(self):
        # logit只打日志,不做别的
        pass
        
    @logit()
    def myfunc1():
        pass

@wraps 装饰器

作用

消除装饰器的副作用,保留被装饰函数的原有属性。

from functools import wraps

def decorator(func):
    @wraps(func)
    def fun_test():
        """fun_test"""
        print("fun_test")

    return fun_test

@decorator
def fun():
    """fun """

    print("fun")

# fun = decorator(fun)
print(fun.__name__)
fun()
print(fun.__name__)
使用场景
  1. 权限使用
from functools import wraps
 
def requires_auth(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        auth = request.authorization
        if not auth or not check_auth(auth.username, auth.password):
            authenticate()
        return f(*args, **kwargs)
    return decorated
  1. 日志使用场景
from functools import wraps
 
def logit(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print(func.__name__ + " was called")
        return func(*args, **kwargs)
    return with_logging
 
@logit
def addition_func(x):
   """Do some math."""
   return x + x
 
 
result = addition_func(4)
# Output: addition_func was called

代码解释:

1689489300652.png

@classmethod 装饰器

使用了@classmethod的函数,可以不用实例化类对象,直接进行调用。第一个参数为类而不是实例。

class MathUtils:
    pi = 3.14159

    @classmethod
    def circle_area(cls, radius):
        return cls.pi * radius**2

    @staticmethod
    def square_area(side_length):
        return side_length**2
    
    # 直接通过类调用方法
    area = MathUtils.circle_area(5)
    print(area)  # 输出: 78.53975

@staticmethod 装饰器

@staticmethod: 将方法转换为静态方法,不传递类或实例的引用。静态方法不需要访问类的属性或方法

class MathUtils:
    pi = 3.14159

    @staticmethod
    def add_numbers(num1, num2):
        return num1 + num2

    @staticmethod
    def square_area(side_length):
        return side_length**2
    
    # 直接通过类调用静态方法
    result = MathUtils.add_numbers(3, 5)
    print(result)  # 输出: 8

    # 创建实例并通过实例调用静态方法
    math = MathUtils()
    area = math.square_area(4)
    print(area)  # 输出: 16

@property 装饰器

@property:将方法转换为属性,调用时可以用访问属性的时候直接调用,而不是访问方法的时候加上()

使用场景
  1. 封装属性的读取和设置方法:使用 @property 装饰器可以将类中的属性封装起来,通过定义 getter 和 setter 方法来控制属性的读取和设置行为。这可以帮助保证属性的合法性和一致性,同时提供更好的代码封装和可维护性。

  2. 计算属性:有时候,某个属性的值并不是直接存储在实例变量中,而是依据其他属性或方法进行计算得到的。在这种情况下,可以使用 @property 装饰器将计算逻辑封装在一个方法中,并通过属性的方式来访问计算结果。这样可以实现动态计算属性值,并隐藏底层的计算细节。

  3. 属性验证和处理:使用 @property 装饰器可以在设置属性值时执行验证和处理逻辑,确保属性值满足特定的条件。例如,可以在设置器方法中检查属性值的类型、范围或其他约束,并根据需要执行相应的处理操作。

  4. 与数据库或外部接口的交互:在与数据库或外部接口进行交互时,有时需要对数据进行一些处理或转换。通过使用 @property 装饰器,可以将这些处理逻辑封装在属性的读取和设置方法中,使得读写操作在调用代码层面更简洁和易用。

  5. 属性的动态计算和更新:在某些情况下,属性的值可能会随着时间、状态或其他因素发生改变。通过将属性定义为带有 @property 装饰器的方法,可以实现属性的动态计算和更新。这样,每次访问属性时都会重新计算属性值,以确保属性的精确性和实时性。

class Person:
    def __init__(self, name, age):
        self._name = name
        self._age = age

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        self._name = value

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, value):
        if value >= 0:
            self._age = value
        else:
            raise ValueError("Age must be a positive integer.")
    
    # 创建 Person 对象
    person = Person("Alice", 25)

    # 通过属性方式获取 name 和 age
    print(person.name)  # 输出: Alice
    print(person.age)   # 输出: 25

    # 通过属性方式设置 name 和 age
    person.name = "Bob"
    person.age = 30

    print(person.name)  # 输出: Bob
    print(person.age)   # 输出: 30

    # 尝试设置无效的 age 值会引发 ValueError 异常
    person.age = -10   # 抛出异常: ValueError: Age must be a positive integer.

@abstractmethod 装饰器

@abstractmethod 是一个装饰器,用于标记一个方法为抽象方法。抽象方法是指在基类中声明但没有具体实现的方法,需要在派生类中进行实现。它主要用于定义类的接口和规范子类的行为。

注意点:

  1. 使用abstractmethod 的类必须为抽象类,抽象类必须导入 from abc import ABC, abstractmethod
  2. 抽象类必须继承ABC
  3. 实现类必须继承抽象类,且必须实现抽象方法
  4. 抽象类不能进行实例化,而是作为其他类的父类
from abc import ABC, abstractmethod

class MyAbstractClass(ABC):
    @abstractmethod
    def my_abstract_method(self):
        pass

    def my_concrete_method(self):
        print("This is a concrete method.")

class MyConcreteClass(MyAbstractClass):
    def my_abstract_method(self):
        print("Implemented abstract method.")

# 实例化具体子类
my_concrete_instance = MyConcreteClass()
my_concrete_instance.my_abstract_method()    # 输出: Implemented abstract method.
my_concrete_instance.my_concrete_method()    # 输出: This is a concrete method.

@try_except 装饰器

@try_except当处理异常的代码块需要在多个函数或方法中共享时,可以使用装饰器来封装异常处理逻辑。通过创建一个装饰器函数,可以将异常处理逻辑应用于特定的函数或方法,以避免在每个函数或方法中重复编写相同的异常处理代码。

def handle_exception(func):
    def wrapper(*args, **kwargs):
        try:
            result = func(*args, **kwargs)
            return result
        except ZeroDivisionError:
            print("除零错误发生")
        except FileNotFoundError:
            print("文件未找到")
        except Exception as e:
            print("发生了一个异常:", str(e))
    return wrapper

@handle_exception
def divide_numbers(num1, num2):
    result = num1 / num2
    print("结果:", result)

@handle_exception
def read_file(file_path):
    with open(file_path, "r") as file:
        contents = file.read()
        print("文件内容:", contents)

# 测试 divide_numbers 函数
divide_numbers(10, 0)  # 输出: 除零错误发生

# 测试 read_file 函数
read_file("nonexistent_file.txt")  # 输出: 文件未找到