• Python三 —— Python迭代器、生成器、装饰器


    3. Python迭代器、生成器、装饰器

    可迭代对象、迭代器和生成器的区别和联系

    画图表示如下:

    在这里插入图片描述

    迭代器和可迭代对象:

    迭代器都是一个可迭代对象,且所有的Iterable(迭代对象)都可以通过内置的iter()转变为Iterator(迭代器)

    生成器和迭代器:

    联系: 所有的生成器都是迭代器,有yield的是生成器,因为yield可以是生成器表达式,也可以是生成器函数。

    区别: 迭代器用于从集合中取出元素,生成器用于凭空生成元素。

    斐波那契数列示例

    函数方法

    def fab(max):
        n,a,b = 0,0,1
        L = []
        while n < max:
            L.append(b)
            a,b = b,a+b
            n += 1
        return L
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    Iterator方法

    为了节省内存,和处于未知输出的考虑,使用迭代器来改善代码。

    class fab(object):
        '''
        Iterator to produce Fibonacci
        '''
        def __init__(self,max):
            self.max = max
            self.n = 0
            self.a = 0
            self.b = 1
            
        def __iter__(self):
            return self
        
        def __next__(self):
            if self.n < self.max:
                r = self.b
                self.a,self.b = self.b,self.a + self.b
                self.n += 1
                return r
            raise StopIteration('Done')
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    迭代器什么都好,就是写起来不简洁。所以用 yield 来改写第三版。

    Generator

    def fab(max):
        n,a,b = 0,0,1
        while n < max:
            yield b
            a,b = b,a+b
            n += 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    使用下面来输出

    for a in fab(8):
        print(a)
    
    • 1
    • 2

    看起来很简洁,而且有了迭代器的特性。

    可迭代对象 (Iterable Object)

    可迭代对象(iterable object),简单来理解就是可以使用for循环来遍历的对象。比如常见的list、set和dict

    可以用以下方法来测试对象是否可迭代:

    >>> from collections import Iterable
    >>> isinstance('abc', Iterable)     # str是否可迭代
    True
    >>> isinstance([1,2,3], Iterable)   # list是否可迭代
    True
    >>> isinstance(123, Iterable)       # 整数是否可迭代
    False
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    迭代器

    概念

    其实当我们对所有的可迭代对象调用dir()方法时,会发现其实他们都定义了__iter__方法。这样就可以通过iter(object)来返回一个迭代器。

    >>> x = [1, 2, 3]
    >>> y = iter(x)
    >>> type(x)
    <class 'list'>
    >>> type(y)
    <class 'list_iterator'>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    可以看到,调用iter()后,变成了一个list_iterator的对象。会发现增加__next__方法。所有实现了__iter____next__两个方法的对象,都是迭代器。

    迭代器是带状态的对象,它会记录当前迭代所在的位置,以便下次迭代的时候获取正确的元素。

    __iter__方法返回迭代器自身

    __next__方法返回容器中的下一个值,如果容器中,没有更多元素了,则抛出StopIteration异常

    >>> x = [1, 2, 3]
    >>> y = iter(x)
    >>> next(y)
    1
    >>> next(y)
    2
    >>> next(y)
    3
    >>> next(y)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    StopIteration
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    判断对象是否是迭代器

    和判断是否是可迭代对象差不多,只需要将Iterable换为Iterator,即:from collections import Iterator,然后调用isinstance方法。

    python中 for循环的本质

    Python中for循环本质上就是通过不断调用next()函数实现的,举个例子,下面的代码:

    x = [1, 2, 3]
    for elem in x:
        ...
    
    • 1
    • 2
    • 3

    实际上执行的是:

    在这里插入图片描述

    也就是先将可迭代对象转换为Iterator,再去迭代。

    这样迭代器只有在调用next()时,才会实际计算到下一个值,起到节省内存的作用。

    常见迭代器

    itertools库提供了很多常见迭代器的使用:

    计数器

    >>> from itertools import count     # 计数器
    >>> counter = count(start=13)
    >>> next(counter)
    13
    >>> next(counter)
    14
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    无限循环列表

    >>> from itertools import cycle
    >>> colors = cycle(['red', 'white', 'blue'])
    >>> next(colors)
    'red'
    >>> next(colors)
    'white'
    >>> next(colors)
    'blue'
    >>> next(colors)
    'red'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    生成器 (generator)

    概念

    Python汇总,一边循环,一边计算的机制,称之为生成器: generator

    常见生成器

    生成器列表

    要创建一个 generator ,最简单的方法是改造列表生成式

    >>> [x*x for x in range(10)]
    [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
    
    >>> (x * x for x in range(10))
    <generator object <genexpr> at 0x03804630>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    生成器函数

    通过def定义,然后通过yield来支持迭代器协议,所以比迭代器写起来看着简单

    >>>def spam():
           yield"first"
           yield"second"
           yield"third"
    >>> spam
    <function spam at 0x011F32B0>
    >>> gen
    <generator object spam at 0x01220B20>
    >>> gen.next()
    'first'
    >>> gen.next()
    'second'
    >>> gen.next()
    'third'
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    一般情况下,生成器函数通过for来使用,这样不用关心StopIteration的异常。

    >>>for x in spam():
           print x
            
    first
    second
    third
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    进行函数调用的时候,返回一个生成器对象。在使用next()调用的时候,遇到yield就返回,记录此时函数调用的位置,下次调用next()时,从断点处开始。

    装饰器 (Decorator)

    装饰器(Decorator)是Python中最吸引人的特征,装饰器本质上是一个函数,它可以让已有的函数不作任何改动的情况下,增加功能

    因此装饰器非常适合有切面需求的场景,比如权限校验,日志记录和性能测试等等。比如像要执行某个函数前,记录日志或者记录时间来统计性能,又不想改动这个函数,就可以通过装饰器来实现。

    不用装饰器,我们需要在函数执行前插入日志,如下:

    def foo():
        print('i am foo')
        
    def foo():
        print('foo is running')
        print('i am foo')
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    虽然这样写,满足了需求,但是修改了原有代码,如果其他函数也需要插入日志的话,就需要修改所有的代码,不能复用代码,可以这么写

    def use_logg(func):
        logging.warn("%s is running" % func.__name__)
        func()
    
    def bar():
        print('i am bar')
    
    use_log(bar)    #将函数作为参数传入
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这样写的确可以复用插入的日志,缺点就是显示的封装原来的函数,我们希望透明的做这件事。用装饰器来写,如下:

    bar = use_log(bar)def use_log(func):
        def wrapper(*args,**kwargs):
            logging.warn('%s is running' % func.__name___)
            return func(*args,**kwargs)
        return wrapper
    
    def bar():
        print('I am bar')
        
    bar = use_log(bar)
    bar()
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    use_log() 就是装饰器,它把真正我们想要执行的函数 bar() 封装在里面,返回一个封装了加入代码的新函数,看起来就像是 bar() 被装饰了一样。

    这个例子中的切面就是函数进入的时候,在这个时候,我们插入了一句记录日志的代码。这样写还是不够透明,通过@语法糖来起到 bar = use_log(bar) 的作用。

    bar = use_log(bar)def use_log(func):
        def wrapper(*args,**kwargs):
            logging.warn('%s is running' % func.__name___)
            return func(*args,**kwargs)
        return wrapper
    
    @use_log
    def bar():
        print('I am bar')
        
    @use_log
    def haha():
        print('I am haha')
        
    bar()
    haha()
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    这样看起来就很简洁,而且代码很容易复用。可以看成是一种智能的高级封装。

    装饰器也是可以带参数的,这为装饰器提供了更大的灵活性

    def use_log(level):
        def decorator(func):
            def wrapper(*args, **kwargs):
                if level == "warn":
                    logging.warn("%s is running" % func.__name__)
                return func(*args)
            return wrapper
    
        return decorator
    
    @use_log(level="warn")
    def foo(name='foo'):
        print("i am %s" % name)
    
    foo()
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    实际上是对装饰器的一个函数封装,并返回一个装饰器。这里涉及到作用域的概念,可以把它看成一个带参数的闭包。

    当使用 @use_log(level='warn') 时,会将 level 的值传给装饰器的环境中。它的效果相当于 use_log(level='warn')(foo) ,也就是一个三层的调用。

    这里有一个美中不足,decorator 不会改变装饰的函数的功能,但会悄悄的改变一个 __name__ 的属性(还有其他一些元信息),因为 __name__ 是跟着函数命名走的。可以用 @functools.wraps(func) 来让装饰器仍然使用 func 的名字。比如

    import functools
    
    def log(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print('call %s():' % func.__name__)
            return func(*args, **kw)
        return wrapper
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    functools.wraps 也是一个装饰器,它将原函数的元信息拷贝到装饰器环境中,从而不会被所替换的新函数覆盖掉。

    有了装饰器,我们就可以剥离出大量与函数功能本身无关的代码,增加了代码的重用性。

  • 相关阅读:
    PV、EV、AC、BAC、EAC、ETC等计算公式含义
    Docker 安装RabbitMq
    [网鼎杯 2020 青龙组]AreUSerialz
    基于卷积神经网络的猫种类的识别
    LeetCode 热题 HOT 100 第七十七天 399. 除法求值 中等题 用python3求解
    什么情况?周鸿祎和三六零违约,“零元”甩卖哪吒汽车10亿元股权
    34. 在排序数组中查找元素的第一个和最后一个位置
    配置cri-docker使kubernetes1.24以docker作为运行时
    【QML】QML与C++混合编程,结构体参数互相传递
    un9.2:创建springboot的两种方式。
  • 原文地址:https://blog.csdn.net/weixin_43662553/article/details/125625271