• Python装饰器进阶:深入理解与最佳实践


    1、什么是装饰器

    https://docs.python.org/zh-cn/3.7/glossary.html#term-decorator
    官网介绍的很简单,返回值为另一个函数的函数,通常使用 @wrapper 语法形式来进行函数变换。装饰器就是闭包的应用,是用来**装饰(修改或增强)**函数、方法、类。

    import time
    
    
    def runtime(function):
        """统计运行时间"""
    
        def wrapper():
            """装饰函数"""
            start_time = time.time()
            function()
            print(f"runtime is {time.time() - start_time}")
    
        return wrapper
    
    
    def fetch_http_data():
        print('开始请求网络数据')
        time.sleep(1)
        print('数据请求完成')
    
    @runtime
    def parse_response_data():
        """解析数据"""
        print('开始解析数据')
        time.sleep(0.5)
        print('数据解析完成')
    
    
    # 把函数当作参数传到另一个函数中执行,但是这种会改变调用方式
    decorator = runtime(fetch_http_data)
    decorator()
    
    # 使用语法糖,不会改变调用方式
    parse_response_data()
    
    # 被装饰的函数,查看函数名的时候 变成了wrapper,所以装饰器会改变原函数的一些属性
    print(parse_response_data.__name__) # wrapper
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    2、保留装饰器中函数的元数据

    parse_response_data.**name** #wrapper
    parse_response_data.**doc** # “”“装饰函数”“”
    被装饰的函数,查看函数名的时候 变成了wrapper,函数的文档注释也改变了, 所以装饰器会改变原函数的一些属性,如何保留原函数的属性呢?

    from functools import wraps
    
    # @wrap,它会帮助保留原函数的元信息
    # @wraps 有一个重要特征是它能让你通过属性 __wrapped__ 直接访问被包装函数
    # parse_response_data.__wrapped__() 可以调用原函数
    
    def runtime(function):
        """统计运行时间"""
    
        @wraps(function)
        def wrapper():
            start_time = time.time()
            function()
            print(f"runtime is {time.time() - start_time}")
    
        return wrapper
    
    @runtime
    def parse_response_data():
        print('开始解析数据')
        time.sleep(0.5)
        print('数据解析完成')
    
    print(parse_response_data.__name__)  # parse_response_data
    print(parse_response_data.__doc__)  # 解析数据
    # 通过__wrapped__属性调用原函数 
    parse_response_data.__wrapped__()
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    3、带参数的装饰器

    装饰器可以带参数,这样可以使装饰器更加灵活和通用,根据不同的情况对被装饰的函数应用不同的行为

    def retry(max_retries=3):
        def decorator(func):
            def wrapper(*args, **kwargs):
                for retry_count in range(max_retries):
                    try:
                        result = func(*args, **kwargs)
                        return result
                    except Exception as e:
                        if retry_count < max_retries:
                            print(f"Retrying {func.__name__} (attempt {retry_count + 1}/{max_retries})...")
                            time.sleep(2)
                        else:
                            raise e
    
            return wrapper
    
        return decorator
    
    
    @retry(max_retries=2)
    def potentially_failing_function():
        import random
        if random.random() < 0.7:
            print("Function succeeded!")
        else:
            raise Exception("Function failed.")
    
    
    potentially_failing_function()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    retry 装饰器接受一个 max_retries 参数,用于指定最大的重试次数。decorator 函数接受被装饰的函数 func,并定义了 wrapper 包装函数,该包装函数尝试执行 func,如果遇到异常则进行重试,最多尝试 max_retries 次。
    然后,potentially_failing_function 函数应用了带参数的装饰器,允许在最多 2 次重试之后终止或成功执行。

    4、 带可选参数的装饰器

    import time
    
    def timing_decorator(func=None, message="Execution time"):
        def decorator(wrapped_func):
            def wrapper(*args, **kwargs):
                start_time = time.time()
                result = wrapped_func(*args, **kwargs)
                end_time = time.time()
                execution_time = end_time - start_time
    
                print(f"{message}: {execution_time} seconds")
                
                return result
    
            return wrapper
    
        if func is None:
            return decorator
        else:
            return decorator(func)
    
    @timing_decorator(message="Function 1 took")
    def function1():
        time.sleep(2)
        print("Function 1 completed.")
    
    @timing_decorator
    def function2():
        time.sleep(1)
        print("Function 2 completed.")
    
    function1()
    function2()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    5、 用类实现装饰器

    除了使用函数实现装饰器外,类也可以实现装饰器,

    import time
    import functools
    
    
    class runtime:
        def __init__(self, func):
            self.func = func
            # 保留被装饰函数的元数据
            functools.update_wrapper(self, func)
    
        def __get__(self, instance, owner):
            if instance is None:
                return self
            # 创建一个可调用的对象,将 instance作为self的参数传递进去
            return functools.partial(self, instance)
    
        def __call__(self, *args, **kwargs):
            start_time = time.time()
            result = self.func(*args, **kwargs)
            end_time = time.time()
            execution_time = end_time - start_time
            print(f"{self.func.__name__} took {execution_time} seconds")
            return result
    
    
    @runtime
    def some_function():
        time.sleep(2)
        print("Function completed.")
    
    
    class Animal:
        @runtime
        def walk(self, road):
            time.sleep(2)
            print(f"{self.__class__} walk {road}")
    
    
    some_function()
    a = Animal()
    a.walk('马路')
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    6、 类中的方法实现装饰器

    # 类中的普通方法实现一个装饰器
    class DecoratedMeta:
    
        def runtime(self, function):
            """统计运行时间"""
    
            @functools.wraps(function)
            def wrapper(*args, **kwargs):
                """装饰函数"""
                start_time = time.time()
                result = function(*args, **kwargs)
                print(f"runtime is {time.time() - start_time}")
                return result
    
            return wrapper
    
        @classmethod
        def runtime_cls(cls, function):
            @functools.wraps(function)
            def wrapper(*args, **kwargs):
                print('使用类方法的装饰器')
                return function(*args, **kwargs)
    
            return wrapper
    
    
    d = DecoratedMeta()
    
    
    @d.runtime  # 使用装饰器
    def add(x, y):
        return x + y
    
    
    @d.runtime_cls
    def sub(x, y):
        return x - y
    
    
    result = add(2, 3)
    print(result)
    result = sub(4, 5)
    print(result)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    7、 给类加上装饰器

    1、给类中的方法加装饰器

    import time
    
    
    def runtime(function):
        """统计运行时间"""
    
        def wrapper(*args, **kwargs):
            """装饰函数"""
            start_time = time.time()
            function(*args, **kwargs)
            print(f"runtime is {time.time() - start_time}")
    
        return wrapper
    
    
    class Animal:
        @runtime
        def walk(self):
            time.sleep(2)
            print(f"{self.__class__} walk")
    
    
    a = Animal()
    a.walk()
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    2、给类加装饰器,扩充类的功能

    # 定义一个装饰器函数
    def log_decorator(cls):
        # 保存原始类的构造函数
        original_init = cls.__init__
    
        # 定义一个新的构造函数,扩充功能
        def new_init(self, *args, **kwargs):
            # 首先调用原始构造函数
            original_init(self, *args, **kwargs)
    
            # 扩展功能:在构造对象时打印信息
            print(f"创建 {self.__class__.__name__}")
    
        # 将新的构造函数替换原始构造函数
        cls.__init__ = new_init
    
        return cls
    
    
    # 使用装饰器扩充类的功能
    @log_decorator
    class MyClass:
        def __init__(self, x, y):
            self.x = x
            self.y = y
    
        def add(self):
            return self.x * self.y
    
    
    # 创建类的实例
    obj = MyClass(3, 4)
    
    # 扩充功能生效,构造对象时打印信息
    result = obj.add()
    print(result)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    8、装饰器的叠加

    import time
    from functools import wraps
    
    
    def runtime(function):
        """统计运行时间"""
    
        @wraps(function)
        def wrapper(*args, **kwargs):
            """装饰函数"""
            start_time = time.time()
            result = function(*args, **kwargs)
            print(f"runtime is {time.time() - start_time}")
            return result
    
        return wrapper
    
    
    def printlog(function):
        """
        函数运行日志
        :param function:
        :return:
        """
    
        @wraps(function)
        def wrapper(*args, **kwargs):
            print(f"{function.__name__} start")
            result = function(*args, **kwargs)
            print(f"{function.__name__} over")
            return result
    
        return wrapper
    
    
    @printlog
    @runtime
    def add(x, y):
        time.sleep(0.5)
        return x + y
    
    
    def sub(x, y):
        return x - y
    
    
    # 调用过程
    # a = runtime(add)
    # b = printlog(a)
    # b(1,3)
    
    add(1, 3)
    
    a = runtime(sub)
    b = printlog(a)
    res = b(1, 3)
    print(res)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    9、 内置的装饰器

    @classmethod
    把一个方法封装成类方法。

    # python的redis第三方库中,使用url连接redis时定义的from_url是一个类方法。
    class Redis(object):
    	""""""
        @classmethod
        def from_url(cls, url, db=None, **kwargs):
            """"""
            connection_pool = ConnectionPool.from_url(url, db=db, **kwargs)
            return cls(connection_pool=connection_pool)
    
    # 调用类方法
    redis_ints = Redis.from_url('redis://user:password@127.0.0.1:6379/0')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    @staticmethod
    将方法转换为静态方法。

    import math
    
    
    class CrawlSite:
    	# 使用静态方法计算页数,与实例无关,工具方法
        @staticmethod
        def get_page(total, offsize):
            """
            计算要爬取的页数
            """
            return math.ceil(total / offsize)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    @property
    会将方法转化为一个具有相同名称的只读属性的 “getter”,特征属性对象具有 getter, setter 以及 deleter 方法,它们可用作装饰器来创建该特征属性的副本,并将相应的访问函数设为所装饰的函数

    class Animal(object):
        def __init__(self, eat):
            self.__eat = eat
    
        # 只有@property时属性不能赋值操作
        @property
        def eat(self):
            return self.__eat
    
        @eat.setter
        def eat(self, value):
            # 设置属性值,同时可以做校验、计算等
            if not isinstance(value, str):
                raise TypeError('Expected a string')
            self.__eat = value
    
        @eat.deleter
        def eat(self):
            del self.__eat
    
    
    a = Animal('rot')
    print(a.eat)
    a.eat = 'cao'
    print(a.eat)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    @functools.wraps
    保留被装饰的函数元信息,用于在定义包装器函数时发起调用 update_wrapper() 作为函数装饰器
    @functools.lru_cache
    一个为函数提供缓存功能的装饰器,如果调用相同,则直接返回缓存中的值,不需要重新计算。用以节约高开销或I/O函数的调用时间。
    如果 maxsize 设置为 None ,LRU功能将被禁用且缓存数量无上限。由于使用了字典存储缓存,所以该函数的固定参数和关键字参数必须是可哈希的。

    @lru_cache(maxsize=100)  
    def fibonacci(n):  
        if n < 2:  
            return n  
        return fibonacci(n-1) + fibonacci(n-2)  
      
    print(fibonacci(10))  # 输出 55  
    print(fibonacci(10))  # 输出 55,不会重新计算
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    @functools.singledispatch
    实现只有第一个参数可接受不同类型的函数

    from functools import singledispatch
    
    
    @singledispatch
    def calculate_area(argument):
        raise NotImplementedError('Unsupported operand type')
    
    
    @calculate_area.register(int)
    def _(argument):
        return argument * argument
    
    
    @calculate_area.register(str)
    def _(argument):
        return int(argument) * int(argument)
    
    
    print(calculate_area(5))  # 输出 25
    print(calculate_area('6'))  # 输出 36
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    @contextlib.contextmanager
    它可以定义一个支持 with 语句上下文的工厂函数, 而不需要创建一个类或区 enter()exit() 方法。

    import contextmanager
    import time
    
    
    def adds():
        for i in range(3):
            print(i)
            time.sleep(1)
    
    
    @contextlib.contextmanager
    def timing_context(func):
        start_time = time.time()
    
        try:
            func()
            yield 'runtime'  # 进入上下文 yield后面的值,就会赋在 with语句的as 后面
        finally:
            end_time = time.time()
            elapsed_time = end_time - start_time
            print(f"Elapsed time: {elapsed_time} seconds")
    
    
    # 使用上下文管理器来测量代码块的执行时间
    with timing_context(adds) as msg:
        # 模拟耗时操作
        print(msg)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    10、 自定义常用的装饰器

    重试机制

    import functools
    import time
    
    
    def retries(max_retries=3, delay=1):
        def decorator(func):
            @functools.wraps(func)
            def wrapper(*args, **kwargs):
                retry_count = 0
                while retry_count < max_retries:
                    try:
                        return func(*args, **kwargs)
                    except Exception as e:
                        print(f"Error: {func.__name__} failed with {e}. Retrying in {delay} seconds...")
                        retry_count += 1
                        time.sleep(delay)
                raise Exception(f"Error: {func.__name__} failed after {max_retries} retries.")
    
            return wrapper
    
        return decorator
    
    
    @retries()
    def some_function():
        # Some code that might fail.
        print('----------------')
    
    
    @retries(max_retries=5, delay=3)
    def another_function():
        # Some code that might fail.
        print('=============')
        raise
    
    
    some_function()
    
    another_function()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    超时判断

    import time
    
    import functools
    from concurrent import futures
    
    pool = futures.ThreadPoolExecutor(1)
    
    
    def runtime(seconds):
        def decorator(func):
            @functools.wraps(func)
            def wrapper(*args, **kw):
                future = pool.submit(func, *args, **kw)
                return future.result(timeout=seconds)
    
            return wrapper
    
        return decorator
    
    
    @runtime(3)
    def request_http():
        time.sleep(2)
        return 111
    
    
    res = request_http()
    print(res)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
  • 相关阅读:
    关于linux系统can收发,以及jetson系列can收发的说明,以及SN65HVD230 CAN board和MCP2515和TJA1050的区别是什么?
    本、硕、博区别真的辣么大吗?
    SpringMVC常用注解总结
    让她/他心动的520/521告白方法(9个方法+链接+代码,原生HTML+CSS+JS实现)
    Vscode LinuxC++环境配置
    macbook本地部署 pyhive环境连接 hive用例
    前端(二十二)——前端工程化
    什么是shell?模拟实现shell(深刻理解shell的内建命令)
    可持久化数据结构——最大异或和(trie)+第K小数(线段树)
    【JavaScript面试】网页轮播图的制作
  • 原文地址:https://blog.csdn.net/u010442378/article/details/134080033