Python装饰器

序言

类似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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值