序言
类似Java语言面向切面编程(AOP,Aspect Oriented Programming)的作用对方法进行环绕增强,Python也有装饰器(Decorator)。
装饰器在Python中有广泛的应用,比如实现身份验证、缓存、性能监测、日志记录等功能。
内置装饰器
Python内置的一些函数和模块,比如property、classmethod、staticmethod、functools.lru_cache等,都是用装饰器实现的。
@staticmethod
builtins.staticmethod
内置函数用于为Python类定义静态方法。静态方法通常用于执行一组相关任务的实用程序类中,如数学计算。通过将相关函数组织成类的静态方法,使代码变得更加有组织、更容易理解。
比如下面在Demo类中定义一个静态方法用于计算,如果在Demo类中不加@staticmethod
,那么定义的calculate
方法第一个参数必须传self
,即类的实例对象,但它对于该函数并没有任何作用。
class Demo:
@staticmethod
def calculate(a, b):
return a + b
if __name__ == "__main__":
demo = Demo()
print(demo.calculate(1, 1))
@classmethod
builtins.classmethod
内置函数用于定义类方法。类方法是与类相关联的方法,而不是与类的实例相关联的方法,第一个参数是类本身。它和不加@classmethod的方法有啥区分,可以看一下下面这个例子,
class Demo:
@classmethod
def class_method(cls, params):
print("this is a class method, class = {%s}, params = {%s}" % (cls, params))
def normal_method(self, params):
print("this is a normal method, self = {%s}, params = {%s}" % (self, params))
if __name__ == "__main__":
demo = Demo()
demo.class_method("test")
demo.normal_method("test")
打印结果如下,
this is a class method, class = {<class '__main__.Demo'>}, params = {test}
this is a normal method, self = {<__main__.Demo object at 0x10cf56ee0>}, params = {test}
@property
builtins.property
内置函数用于保护类的封装特性,并可以获取该属性的值,设置属性的值,删除属性的值。
比如共有的属性变量可以通过下面这种方式获取修改,
class Demo:
def __init__(self, params):
self.params = params
if __name__ == "__main__":
demo = Demo(20)
print(demo.params)
demo.params = 50
print(demo.params)
那么如果是私有的变量(以两个下划线为前缀定义的变量)就不可以通过上面的这种方式获取了。
以双下划线(__)作为前缀的变量或函数被认为是特殊属性或魔法方法。这些特殊属性或魔法方法通常用于内部操作、控制对象行为等目的。
使用@property
可以直接通过thread_number
函数获取私有属性值,但是属性thread_number
不能被修改,
class Demo:
def __init__(self, params):
self.__params = params
@property
def thread_number(self):
return self.__params
if __name__ == "__main__":
demo = Demo(20)
print(demo.thread_number)
# Property 'thread_number' cannot be set
demo.name = 50
print(demo.thread_number)
如果想要私有属性可以设置值,则可以增加一个函数加上@函数名.setter
,使其指定属性具有setter属性的方法入口,类似于Java中的getter()方法和setter()方法,
class Demo:
def __init__(self, params):
self.__params = params
@property
def thread_number(self):
return self.__params
@thread_number.setter
def thread_number(self, value):
self.__params = value
if __name__ == "__main__":
demo = Demo(20)
print(demo.thread_number)
# Property 'thread_number' cannot be set
demo.thread_number = 50
print(demo.thread_number)
同理,可以增加@函数名.deleter
,使其指定属性具有的删除该属性的方法入口。
@cached_property
functools.cached_property
函数用于将类的方法转换为一个属性,计算出该属性的值之后,将其作为实例的普通属性放入缓存。
@lru_cache
functools.lru_cache
函数用于缓存计算结果,LRU(Least Recently Used)即最不常用策略。当缓存已满且需要存储新结果时,会将最近使用得最少的结果从缓存中删除,为新缓存腾出空间。
import time
from functools import lru_cache
def fibonacci_normal(n):
if n < 2:
return n
return fibonacci_normal(n - 1) + fibonacci_normal(n - 2)
@lru_cache
def fibonacci_cache(n):
if n < 2:
return n
return fibonacci_cache(n - 1) + fibonacci_cache(n - 2)
if __name__ == "__main__":
start_time = time.perf_counter()
fibonacci_normal(30)
end_time = time.perf_counter()
print(f"\n====== The normal execution time: {end_time - start_time:.8f} seconds ======")
start_time = time.perf_counter()
fibonacci_cache(30)
end_time = time.perf_counter()
print(f"\n====== The lru cache execution time: {end_time - start_time:.8f} seconds ======")
时间开销结果如下,明显lru缓存耗时更短,耗时短不是因为最近最少使用算法的效果,而是因为缓存的效果,递归计算斐波那契数列时,由于后面递归计算时会用到前面已经计算过的结果,所以如果把原来已经计算过的结果直接缓存起来,后面直接使用效率更高。
====== The normal execution time: 0.25758476 seconds ======
====== The lru cache execution time: 0.00006704 seconds ======
@total_ordering
functools.total_ordering
函数用于自动为类生成比较运算符方法。比如下面Student
类没有对__ge__
、__gt__
和__le__
方法进行定义,使用@total_order
装饰器就不会因为未定义le(less than or equal to)导致报错'<=' not supported between instances of 'Student' and 'Student'
或其他比较错误。
from functools import total_ordering
@total_ordering
class Student:
def __init__(self, name, grade):
self.__name = name
self.__grade = grade
def __eq__(self, other):
return self.__grade == other.__grade
def __lt__(self, other):
return not self.__grade < other.__grade
if __name__ == "__main__":
student1 = Student("Alice", 80)
student2 = Student("Bob", 69)
student3 = Student("Tom", 98)
print(student1 < student2)
print(student1 > student2)
print(student1 == student3)
print(student1 <= student3)
print(student1 >= student3)
@contextmanager
contextlib.contextmanager
函数用于上下文管理器,它的核心就是__enter__
和__exit__
函数,__enter__
函数中做资源的初始化,在__exit__
函数中做资源的回收,Python会自动帮你调用这两个函数,确保它们在适当的时候被调用,类似于Java中的try-with-resources
语法糖。
from contextlib import contextmanager
@contextmanager
def file_manager(filename, mode):
print("The file is opening...")
this_file = open(filename, mode)
yield this_file
print("The file is closing...")
this_file.close()
if __name__ == "__main__":
with file_manager('test.txt', 'w') as file:
file.write('Yang is writing!')
自定义装饰器
import time
def timer(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"{func.__name__} took {end_time - start_time:.2f} seconds to execute.")
return result
return wrapper
class Demo:
def fibonacci(self, n):
if n < 2:
return n
return self.fibonacci(n - 1) + self.fibonacci(n - 2)
@timer
def data_processing_function(self):
self.fibonacci(30)
if __name__ == "__main__":
demo = Demo()
demo.data_processing_function()
结果是执行Demo类的data_processing_function
方法时,会执行自定义函数,打印出结果data_processing_function took 0.26 seconds to execute.
参考链接
1、https://docs.python.org/3.10/library/abc.html
2、https://zhuanlan.zhihu.com/p/602458039
3、https://www.jianshu.com/p/d09778f4e055