• 【6k字】详解Python装饰器和生成器


    1 Python装饰器

    1.1 什么是装饰器

    在 Python 中,装饰器提供了一种简洁的方式,用来修改或增强函数和类的行为。装饰器在语法上表现为一个前置于函数或类定义之前的特殊标记:

    @simple_decorator
    def hello_world():
        print("Hello, world!")
    
    • 1
    • 2
    • 3

    在这个例子中,simple_decorator 是一个装饰器,它作用于下方的 hello_world 函数。装饰器在概念上就像一个包装器,它可以在被装饰的函数执行前后插入任意的代码,进而改变被装饰函数的行为。

    1.1.1 参数化装饰器

    我们还可以进一步将装饰器参数化,这让装饰器的行为更具灵活性。比如,我们可以定义一个装饰器,让它在函数执行前后打印自定义的消息:

    def message_decorator(before_message, after_message):
        def decorator(func):
            def wrapper(*args, **kwargs):
                print(before_message)
                result = func(*args, **kwargs)
                print(after_message)
                return result
            return wrapper
        return decorator
    
    @message_decorator("Start", "End")
    def hello_world():
        print("Hello, world!")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这个例子中,message_decorator 是一个参数化装饰器,它接受两个参数,分别代表函数执行前后要打印的消息。

    1.2 装饰器的工作原理

    在 Python 中,函数是第一类对象。这意味着函数和其他对象一样,可以作为变量进行赋值,可以作为参数传给其他函数,可以作为其他函数的返回值,甚至可以在一个函数里面定义另一个函数。这个特性是实现装饰器的基础。

    def decorator(func):
        def wrapper():
            print('Before function execution')
            func()
            print('After function execution')
        return wrapper
    
    def hello_world():
        print('Hello, world!')
    
    decorated_hello = decorator(hello_world)
    decorated_hello()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这个例子中,decorator 函数接收一个函数 hello_world 作为参数,并返回了一个新的函数 wrapped_func。这个新函数在 hello_world 函数执行前后分别打印一条消息。我们可以看到,装饰器实际上是一个返回函数的函数

    1.2.1 函数签名保持

    默认情况下,装饰器会“掩盖”掉原函数的名字和文档字符串。这是因为在装饰器内部,我们返回了一个全新的函数。我们可以使用 functools.wraps 来解决这个问题:

    import functools
    
    def decorator(func):
        @functools.wraps(func)
        def wrapper():
            print('Before function execution')
            func()
            print('After function execution')
        return wrapper
    
    @decorator
    def hello_world():
        "Prints 'Hello, world!'"
        print('Hello, world!')
    
    print(hello_world.__name__)
    print(hello_world.__doc__)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    这样,使用装饰器后的函数名和文档字符串能够保持不变。

    1.3 装饰器的应用实例

    装饰器在实际的 Python 编程中有许多应用场景,比如日志记录、性能测试、事务处理、缓存、权限校验等。

    1.3.1 日志记录

    一个常见的应用就是使用装饰器进行日志记录

    import logging
    
    def log_decorator(func):
        logging.basicConfig(level=logging.INFO)
        
        def wrapper(*args, **kwargs):
            logging.info(f'Running "{func.__name__}" with arguments {args} and kwargs {kwargs}')
            result = func(*args, **kwargs)
            logging.info(f'Finished "{func.__name__}" with result {result}')
            return result
        
        return wrapper
    
    @log_decorator
    def add(x, y):
        return x + y
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    这个装饰器记录了函数的名称,函数调用的参数,以及函数返回的结果。

    1.3.2 装饰器链

    Python 允许我们将多个装饰器应用到一个函数上,形成一个装饰器链。

    例如,我们可以同时应用日志装饰器和性能测试装饰器

    import time
    import logging
    from functools import wraps
    
    def log_decorator(func):
        logging.basicConfig(level=logging.INFO)
        
        @wraps(func)
        def wrapper(*args, **kwargs):
            logging.info(f'Running "{func.__name__}" with arguments {args} and kwargs {kwargs}')
            result = func(*args, **kwargs)
            logging.info(f'Finished "{func.__name__}" with result {result}')
            return result
    
        return wrapper
    
    def timer_decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            start_time = time.time()
            result = func(*args, **kwargs)
            end_time = time.time()
            print(f'Function "{func.__name__}" took {end_time - start_time} seconds to run.')
            return result
    
        return wrapper
    
    @log_decorator
    @timer_decorator
    def add(x, y):
        time.sleep(2)
        return x + y
    
    • 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

    在这个例子中,@log_decorator@timer_decorator 两个装饰器被同时应用到 add 函数上,它们分别负责记录日志和测量函数运行时间。

    1.3.3 自动注册装饰器

    一个有趣的装饰器应用是自动注册。这个装饰器会在装饰函数时自动将函数添加到一个列表或字典中,这样我们就可以在程序的其他地方访问到这个列表或字典,知道有哪些函数被装饰过。

    # 装饰器将函数注册到一个列表中
    def register_decorator(func_list):
        def decorator(func):
            func_list.append(func)
            return func
        return decorator
    
    # 自动注册函数
    registered_functions = []
    @register_decorator(registered_functions)
    def foo():
        pass
    
    @register_decorator(registered_functions)
    def bar():
        pass
    
    print(registered_functions)  # 输出: [, ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    这个装饰器可以用于自动注册路由、插件系统、命令行参数处理等场景,能够大大提高代码的灵活性和可扩展性。

    1.4 Summary

    Python 装饰器是一种强大的工具,它可以让我们更有效地管理和组织代码,从而在项目中更好地使用装饰器。

    2 Python生成器

    强大的数据处理管道

    生成器是Python的一种核心特性,允许我们在请求新元素时再生成这些元素,而不是在开始时就生成所有元素。它在处理大规模数据集、实现节省内存的算法构建复杂的迭代器模式等多种情况下都有着广泛的应用。在本篇文章中,我们将从理论和实践两方面来探索Python生成器的深度用法。

    2.1 生成器的定义和基本操作

    生成器是一种特殊的迭代器,它们的创建方式是在函数定义中包含yield关键字。当这个函数被调用时,它返回一个生成器对象,该对象可以使用next()函数或for循环来获取新的元素。

    def simple_generator():
        yield "Python"
        yield "is"
        yield "awesome"
    
    # 创建生成器
    gen = simple_generator()
    
    # 使用next函数获取元素
    print(next(gen))  # 输出: Python
    print(next(gen))  # 输出: is
    print(next(gen))  # 输出: awesome
    
    # 使用for循环获取元素
    for word in simple_generator():
        print(word)
    
    # 输出:
    # Python
    # is
    # awesome
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    当生成器耗尽(即没有更多元素产生)时,再次调用next()函数将引发StopIteration异常。这个异常可以由我们手动捕获,或者由for循环自动处理。

    2.2 生成器的惰性求值和内存优势

    生成器的主要优势之一是它们的惰性求值特性。也就是说,生成器只在需要时才计算和产生元素。这使得生成器在处理大规模数据时,可以大大降低内存使用量。与传统的数据结构(如列表)相比,生成器不需要在内存中存储所有元素,而是在每次迭代时动态计算出新的元素。

    这种特性使得生成器在处理大规模数据流、实现复杂的算法或构建动态的数据管道等场景中具有显著的优势。

    # 无限序列生成器
    def infinite_sequence():
        num = 0
        while True:
            yield num
            num += 1
    
    # 创建生成器
    seq = infinite_sequence()
    
    # 输出前10个元素
    for i in range(10):
        print(next(seq))  
    
    # 输出:
    # 0
    # 1
    # 2
    # 3
    # 4
    # 5
    # 6
    # 7
    # 8
    # 9
    
    • 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

    在这个例子中,infinite_sequence是一个永不停止的生成器。尽管它可以产生无穷多的元素,但由于生成器的惰性求值特性,它并不会导致内存耗尽。

    2.3 生成器表达式

    生成器表达式是创建生成器的一种更简洁的方式。它们与列表推导式的语法相似,但是生成的是一个生成器对象,而不是一个完整的列表。这使得生成器表达式在处理大规模数据时可以节省大量的内存。

    # 创建一个生成器表达式
    gen_expr = (x**2 for x in range(1000000))
    
    # 输出前10个元素
    for i in range(10):
        print(next(gen_expr))
    
    # 输出:
    # 0
    # 1
    # 4
    # 9
    # 16
    # 25
    # 36
    # 49
    # 64
    # 81
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这个例子中,gen_expr是一个生成器表达式,它可以生成10^6个元素的平方数。但是,由于生成器表达式的惰性求值特性,它并不会在内存中生成和存储所有这些元素。

    2.4 生成器和协程

    Python的生成器还可以作为协程使用。

    协程是一种特殊类型的函数,它可以在其执行过程中挂起和恢复,从而在单个线程中实现多任务协作式并发。这使得我们可以使用生成器来实现复杂的控制流程,如并发编程异步IO等。

    def coroutine_generator():
        print("Starting")
        while True:
            value = (yield)
            print(f"Received: {value}")
    
    # 创建生成器
    gen = coroutine_generator()
    
    # 启动生成器
    next(gen)  # 输出: Starting
    
    # 向生成器发送数据
    gen.send("Hello")  # 输出: Received: Hello
    gen.send("Python")  # 输出: Received: Python
    
    # 关闭生成器
    gen.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这个例子中,coroutine_generator是一个协程生成器。我们可以使用send()函数向它发送数据,生成器在收到数据后将其打印出来。

    2.5 Python标准库itertools

    在Python的标准库itertools中,有一个函数itertools.islice,它可以用来对生成器进行切片操作,就像我们对列表进行切片那样。这在处理大规模数据流时非常有用。

    import itertools
    
    # 无限序列生成器
    def infinite_sequence():
        num = 0
        while True:
            yield num
            num += 1
    
    # 创建生成器
    seq = infinite_sequence()
    
    # 对生成器进行切片操作
    sliced_seq = itertools.islice(seq, 5, 10)
    
    # 输出切片后的元素
    for num in sliced_seq:
        print(num)
    
    # 输出:
    # 5
    # 6
    #
    
     7
    # 8
    # 9
    
    • 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

    在这个例子中,我们使用itertools.islice函数对无限序列生成器seq进行了切片操作,获取了序列的第5个到第10个元素(从0开始计数)。这让我们能够在不消耗大量内存的情况下,灵活地处理大规模的数据流。

    2.6 Summary

    生成器是Python中一种非常强大的工具,它让我们能够以更高效和简洁的方式处理复杂的问题。熟练掌握生成器的使用,将使你在Python编程中具有更高的自由度和更强的实力。

    Reference

    https://blog.csdn.net/magicyangjay111/article/details/131054330

    https://blog.csdn.net/magicyangjay111/article/details/130965602

  • 相关阅读:
    vector容器中push_back()和emplace_back()函数的区别
    求求你别在用SpringMVC了,太Low了,Spring又官宣了一个更牛逼的替代框架
    系统移植——开发阶段部署
    基于Linux的智能家居(工厂模式)
    个人防护设备视觉检测技术方案与思路探讨: Computer Version based PPE detection
    【pytorch深度学习 应用篇02】训练过程可视化
    网站的常见攻击与防护方法
    你觉得神经网络初始化权重应该设为多少呢?-猛男技术控
    JavaScript与jQuery(下篇)
    c++模板库容器list vector map set操作和性能对比
  • 原文地址:https://blog.csdn.net/JishuFengyang/article/details/133807812