前面提到了装饰器的一些原理和使用方法,这一篇博客专门讲一下有哪些常用的装饰器。
@property
在开发中,经常会需要构建类。构建类的时候是需要为类分配属性的。往往的属性会被写在__init__
方法下,如下:
1 2 3 4
| class Predict: def __init__(self,arg1,arg2): self.arg1 = arg1 self.arg2 = arg2
|
这种方式构建的类实例,可以被轻松赋值更改,例如:
1 2
| predict_instance = Predict(arg1=1,arg2=2) predict_instance.arg1 = 3
|
这样就会使得使用时安全性大大降低。为了解决这个问题,需要引入私有属性。例如:
1 2 3 4
| class Predict: def __init__(self,arg1,arg2): self.__arg1 = arg1 self.__arg2 = arg2
|
这时候就无法访问arg1
这个私有属性了,要怎么访问呢?那就需要定义一个方法去删除:
1 2 3 4 5 6 7 8 9 10 11 12
| class Predict: def __init__(self,arg1,arg2): self.__arg1 = arg1 self.__arg2 = arg2 @property def arg1(self): return self.__age @property def arg2(self): return self.__age
|
通过定义了两个方法,就可以访问到这两个私有变量了。那么这里@property
这个装饰器起到的作用就是把这两个方法变成可以直接调用的属性,这样调用时就不需要像普通方法一样写成arg1()
了。
如果还想修改和删除这些私有变量,就需要再构建新的方法,并使用原方法组装成装饰器,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13
| @arg1.setter def arg1(self, arg1): if isinstance(arg1, int): self.__arg1 = arg1 else: raise ValueError
@arg1.deleter def age1(self): print("删除年龄数据!") predict_instance.arg1 = 3 del predict_instance.arg1
|
通过设置了这两个方法,就可以和之前一样对私有变量进行修改 。
@abstractmethod
@abstractmethod
是一个实现抽象方法的装饰器。所谓抽象方法,就是一个模板,后续继承自该类的类都需要满足abstractmethod
的方法。这是为了模块化功能,使得项目代码更清晰。
1 2 3 4 5 6 7 8 9
| from abc import abstractmethod
class LLM_Base(): def __init__(self): pass @abstractmethod def predict(self): pass
|
在基类不需要实现具体的方法,但需要在子类中实现。
1 2 3 4 5 6
| class OpenAI(LLM_Base) def __init__(self,model): self.model = model def predict(self,message): return (model.predict(message))
|
可以看到OpenAI类继承自LLM_Base,并实现了predict的方法。如果没有实现就会报错。
@classmethod
@classmethod
和后面提到的@staticmethod
有点像,它的作用是让类里的方法可以直接调用类的参数,并且不需要实例化类。也就是说,这个方法不需要self
参数,但是第一个参数需要是cls
,这代表类本身:从原理上来说,这个方法在内部先实例化了类,因此不需要外部显式的实例化。
举一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import datetime
class Person: def __init__(self, name, birthdate): self.name = name self.birthdate = birthdate
def calculate_age(self): today = datetime.date.today() age = today.year - self.birthdate.year - ((today.month, today.day) < (self.birthdate.month, self.birthdate.day)) return age
def display(self): return f"{self.name} is {self.calculate_age()} years old."
@classmethod def from_birthyear(cls, name, birthyear): ""通过输入名字和日期生成一个实例。""" return cls(name, datetime.date(birthyear, 1, 1))
|
上面实现了一个类,这个类要求初始化时输入姓名和出生日期。同时也支持通过类方法from_birthyear
创建实例,其中的cls
不需要显式传递。
1 2 3 4 5 6 7
| # 使用普通的 __init__ 方法创建实例 person1 = Person("Alice", datetime.date(1990, 10, 15)) print(person1.display()) # 输出:Alice is 34 years old.
# 使用 @classmethod 创建实例 person2 = Person.from_birthyear("Bob", 1995) print(person2.display()) # 输出:Bob is 29 years old.
|
网上翻阅了一些相关内容,发现这个classmethod的好处主要是在可以直接调用类的参数。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import math class Pizza(object): def __init__(self, radius, height): self.radius = radius self.height = height @staticmethod def compute_area(radius): return math.pi * (radius ** 2) @classmethod def compute_volume(cls, height, radius): return height * cls.compute_area(radius) def get_volume(self): return self.compute_volume(self.height, self.radius) print(Pizza.compute_volume(12, 2))
>> 150.79644737231007
|
可以看到它可以直接调用类里的静态方法。
@staticmethod
静态方法就很好理解了,相比classmethod
,它不需要实例化类,也不用类里的参数,只是一个单独可以调用的方法。之所以引入它,是为了区分这个函数属于某一类。例如:
1 2 3 4 5 6 7
| class MathUtility: @staticmethod def add_numbers(num1, num2): """Static method to add two numbers.""" return num1 + num2 sum = MathUtility.add_numbers(num1=4,num2=5)
|
@dataclass
@dataclass
在python3.7被引入。它的主要作用就是省略了__init__
这个过程传统写类的时候,需要给类写一个初始化方法:
1 2 3 4
| def __init__(self, name: str, unit_price: float, quantity_on_hand: int = 0): self.name = name self.unit_price = unit_price self.quantity_on_hand = quantity_on_hand
|
而使用了@dataclass
后,只需要这样:
1 2 3 4 5 6 7 8 9 10
| from dataclasses import dataclass
@dataclass class InventoryItem: name: str unit_price: float quantity_on_hand: int = 0
def total_cost(self) -> float: return self.unit_price * self.quantity_on_hand
|
此外,这个装饰器实际上不止实现了__init__
方法,还实现了其他数据相关的魔术方法,例如:__eq__
,__repr__
等等。
我们还可以设置其中的forzen
参数,将类参数设置为只读对象:
1 2 3 4 5 6 7
| from dataclasses import dataclass
@dataclass(frozen=True) class Person: name: str age: int iq: int = 100
|
@overload
overload
主要解决的是重载的概念。正常情况下,当Python同时定义两个同名方法时,后者才是真正调用时会被用到的方法,例如:
1 2 3 4 5 6 7 8
| def quack(): print("Quack: ")
def quack(mark): print(f"Quack: {mark}") quack quack('Hello')
|
如果调用overload
,就可以实现覆盖的效果。最后一个没有overload
装饰器的同名方法将会涵盖之前的方法,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| from typing import overload
class Duck:
@overload def quack(self) -> None: ...
@overload def quack(self, mark: str) -> None: ...
def quack(self, arg=None): if arg: print(f"GaGaGa: {arg}") else: print("GaGaGa!")
d = Duck() d.quack() d.quack("I am a duck~")
|
overload
实际上是一个类型检查的工具。它会为同名的所有方法,分配不同入参时的输出结果的类型。
有的时候,一个方法可能有多种入参的组合,输出的结果类型不一样,这个时候就可以用overload实现。
类似的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| from typing import overload
class MyClass: @overload def my_method(self, x: int) -> int: pass @overload def my_method(self, x: str) -> str: pass
def my_method(self, x): if isinstance(x, int): return x * 2 elif isinstance(x, str): return x.upper()
obj = MyClass()
print(obj.my_method(10)) print(obj.my_method('hello'))
|
理解了一下,用overload
装饰的方法,只需要写它的入参类型和出参类型,具体实现是交给最后的方法实现。这个主要还是让IDE去理解,辅助类型提示。
@singleton
singleton
是一种设计思路,意为单例模式,也就是一个类只能有一个实例。基于这个思路可以手动实现singleton
装饰器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| def singleton(cls): """装饰器实现的 Singleton 模式""" instances = {}
def get_instance(*args, **kwargs): if cls not in instances: instances[cls] = cls(*args, **kwargs) return instances[cls]
return get_instance
@singleton class MyClass: def __init__(self, value): self.value = value
obj1 = MyClass(1) obj2 = MyClass(2)
print(obj1 is obj2) print(obj1.value) print(obj2.value)
|
@lru_cache
@lru_cache
是Python 标准库中 functools
模块提供的一个装饰器,用于实现函数的缓存功能。LRU
代表 “Least Recently Used”(最近最少使用),这意味着当缓存达到其最大大小时,最早缓存的项会被删除以为新的项腾出空间。
使用 @lru_cache
装饰器可以优化那些计算密集型、且参数相同的函数,因为它会缓存函数的结果,避免对相同输入进行重复计算。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13
| from functools import lru_cache
@lru_cache(maxsize=3) def fibonacci(n): if n < 2: return n return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(10))
print(fibonacci(10))
|
@log_results
在运行复杂的函数调用时,跟踪每个函数的输出变得至关重要。因此可以手动实现@log_results
装饰器,来帮助我们记录函数的结果,以便于调试和监控:
1 2 3 4 5 6 7 8 9 10 11 12
| def log_results(func): def wrapper(*args, **kwargs): result = func(*args, **kwargs) with open("results.log", "a") as log_file: log_file.write(f"{func.__name__} - Result: {result}\n") return result return wrapper
@log_results def calculate_metrics(data):
|
2024/1/6 于苏州