• python---基础知识回顾(七)迭代器和生成器


    前戏:迭代器和生成器

    迭代:

    如果给定一个list或tuple,我们可以通过for循环来遍历这个list或tuple,这种遍历我们称为迭代(Iteration)。
    Python的for循环不仅可以用在list或tuple上,还可以作用在其他可迭代对象上(用isinstance判断)

    可以直接作用于for循环的对象统称为可迭代对象

    可以直接作用于for循环的数据类型有以下几种:
    一类是集合数据类型,如list、tuple、dict、set、str等;(称为容器<容器是一种把多个元素组织在一起的数据结构>,很多容器都是可迭代的)
    一类是generator,包括生成器和带yield的generator function。
    这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。

    (一)迭代器

    一个实现了__iter__方法的对象是可迭代的,一个实现了__next__方法的对象则是迭代器

    对于序列和字典的可迭代,是因为在该对象中实现了上面的两个方法

    __iter__方法会返回一个迭代器,而所谓的迭代器就是具有__next__方法的对象。在调用__next__方法时,迭代器会返回他的下一个值。若是next方法被调用牡丹石迭代器中没有值可以返回,就会引发一个StopIteration异常

    迭代器的优点:需要数据就去获取,而不是一次获取全部数据

    相对于我们一次性取出数据,放在列表等类型中,若数据量过大,那么列表会占据大量的内存。而且对于这些数据,我们若是只使用一次就释放的话,那么放在列表中实在是太过浪费内存。
    更好的方法就是使用迭代器。迭代器只会取出当前需要的数据方法内存中。

    例如Django中的queryset惰性机制中就有提及迭代器的好处(在处理大量的数据时)

    栗子:不使用列表的案例,因为如果使用列表,那么列表长度将会是无穷大。占据空间将会是巨大的。

    斐波那契数列:

    class Fibs:
        def __init__(self):
            self.a = 0
            self.b = 1
    
        def __next__(self):
            self.a,self.b = self.b,self.a+self.b
            return self.a
    
        def __iter__(self):
            return self
    
    f = Fibs()
    
    for i in f:
        if i > 1000:
            print(i)    #1597
            break

    补充:内建函数iter可以从可迭代的对象中获取迭代器

    >>> a = [1,2,3,]
    >>> b = iter(a)
    >>> type(b)
    <class 'list_iterator'>
    >>> next(b)
    1
    >>> next(b)
    2
    >>> next(b)
    3
    >>> next(b)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    StopIteration

    可以知道迭代器是一次性消耗品(只会向前获取,不会向后获取),当耗尽时就会触发StopIteration异常
    若是想保留一份数据,可以用deepcopy

    从迭代器中获取序列:

    class Fibs:
        def __init__(self):
            self.a = 0
            self.b = 1
    
        def __next__(self):
            self.a,self.b = self.b,self.a+self.b
            if self.a > 1597:
                raise StopIteration
            return self.a
    
        def __iter__(self):
            return self
    
    f = Fibs()
    
    ls = list(f)
    print(ls) #[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597]

    使用list构造方法显示的将迭代器转换为列表

    class list(object):
            def __init__(self, seq=()): # known special case of list.__init__
            """
            list() -> new empty list
            list(iterable) -> new list initialized from iterable's items
         (若是迭代器,那么新的列表则是迭代器的所有成员,结束是以StopIteration为标志,若是上面没有触发,那么会一直去扩展列表)
    # (copied from class doc) """ pass

     结束


    应该还记得列表推导式(生成式)<顺道回忆下lambda表达式>

    >>> [x for x in range(100) if x % 7 == 0]
    [0, 7, 14, 21, 28, 35, 42, 49, 56, 63, 70, 77, 84, 91, 98]

    通过列表推导式,我们可以直接生成一个列表,同样的,这个列表的内存也是受到限制的,当我们使用列表推导式,一次生成一个超大数量的列表,会占据大量内存,然而,若我们只是访问了前面几个,那么后面的空间占用几乎是无用的。

    for i in [x for x in range(100) if x % 7 == 0]:
        if i < 50:
            print(i)
        else:
            break

    在上面案例中,我们只是想去获取满足条件的数据的一部分。但是在进行循环时,并不会立刻进行,而是需要将列表生成式全部执行后,才允许去进行循环。而我们所需要的数据仅仅是列表中的前一部分,但是列表推导式一次性将数据全部生成。占据大量无用的空间。那么我们是否可以做到像迭代器那样,需要的时候再去获取。从而避免数据冗余

    (二)生成器

    生成器都是迭代器。生成器是一种用普通函数语法定义的迭代器

    创建一个生成器方法有多种:

    其中第一种与列表推导式十分相似,只是需要将中括号[]变为小括号()

    >>> b = [x for x in range(100) if x % 7 == 0]
    >>> type(b)
    <class 'list'>
    [0, 7, 14, 21, 28, 35, 42, 49, 56, 63, 70, 77, 84, 91, 98]
    
    >>> b = (x for x in range(100) if x % 7 == 0)
    >>> type(b)
    <class 'generator'>
    >>> next(b)
    0
    >>> next(b)
    7
    >>> next(b)
    14
    >>> for i in b:
    ... print(i)
    
    当数据全部取出后也会触发StopIteration错误

    另外一种是:任何包括yield语句的函数都可以称为生成器。

    这里同样以斐波那契数列为例:

    def fibs(max):
        n,a,b = 0,0,1
        while n < max:
            yield a  #yield语句
            n += 1
            a, b = b, a+b
    
    
    f = fibs(8)
    
    for i in f:
        print(i)    #0 1 1 2 3 5 8 13

    生成器和普通函数的行为有很大的区别。

    1. 不像return返回一次结果就结束函数,而是可以返回多次结果。从什么for循环可以看出,这一个函数返回了不止一次结果
    2. 每产生一个值(即在yield语句中返回的值),函数就会被冻结,不在执行:即函数停在那点等待被重新唤醒。被重新唤醒后就从之前通知的那点开始执行
    def 函数:
        ...
        yield  1    执行第一次后返回值1后冻结,不在执行,等待第二次
        ...    #执行第二次时会向下继续执行,直到下一次yield
        ...
        ...
        yield  2    #第二次冻结(这个过程包括了执行上面的逻辑语句)
        ...
        ...
        ...
        yield  3

    案例:

    >>> def flatten(nested):
    ...     for sublist in nested:
    ...             for ele in sublist:
    ...                     yield ele
    
    >>> for num in flatten(nested):
    ...     print(num)
    ...
    1
    2
    3
    4
    5
    >>>

    也可以同上面迭代器一样使用list显示转换为列表。

    >>> list(flatten(nested))
    [1, 2, 3, 4, 5]
    >>>

    但是这样会立刻实例化列表,丧失了迭代的优势。

    def nrange(num):
        temp = -1
        while True:
            temp = temp + 1
            if temp >= num:
                return
            else:
                yield temp
    
    for i  in nrange(10):
        print(i)
    含有return的自定义nrange生成器

    通用生成器:

    生成器是一个包含yield关键字的函数。当他被调用的时候,在函数体中的代码不会执行,而是会返回一个迭代器。每次请求一个值,就会执行生成器中的代码,直到遇到一个yield或者return语句。(yield意味着生成一个值,并冻结执行,等待下一次执行。return意味着生成器要通知执行)

    生成器由两部分组成:生成器的函数和生成器的迭代器。生成器的函数使用def语句定义的,包含yield的。生成器的迭代器是这个函数的返回部分。合在一起就是生成器。

    生成器方法:

    生成器中新特征:可以为生成器提供值,而不是只像上面那样生成器为外面返回值。生成器内外可以进行交流

    外部作用域访问生成器的send方法,可以向生成器内部传递消息(任意对象),此时yield不再是一个返回值语句,而是一个表达式

    send方法和yield语句的执行区别:

    yield

    def rep2(val):
    yield val print("aaa") val += 10 print("bbb") yield val r = rep2(33) print(next(r))  #可以看出,执行yield返回值后,就冻结在该条语句,不再向下执行,只有当下一个next出现,才会继续执行
    def rep(val):new = (yield val)  #接收send发送过来的数据
            if new:
                print(new)
            new = (yield val)
            if new:
                print(new)
    
    f = rep(33)
    v = next(f)
    
    f.send("dsad")  #会打印出来dasd  可以看出,当send发送数据后,在接收数据后,会继续向下执行,直到下一个yield表达式出现

    send方法使用:

    注意:在使用send方法时,只有当生成器挂起以后才有意义(也就是说:在yield函数第一次执行之后)

    def rep(val):
            new = (yield val)
            if new:
                print(new)
            new = (yield val)
            if new:
                print(new)

    开始执行:

    若没有将生成器挂起:

    TypeError: can't send non-None value to a just-started generator

    所以,我们在使用时需要先挂起生成器。挂起方法有两种:

    第一种:

    r = rep(33)
    v = next(r)  #这里正常执行next获取yield返回,后面就可以正常使用send
    print(v)
    
    r.send("dsad")
    r.send("dsadds")

    第二种(由刚刚的TypeError可以知道不能send一个非None值,在第一次时,所以我们可以直接在第一次时send(None)):

    r.send(None)
    r.send("dsad")
    r.send("dsadds")

    两种方法,强烈推荐第二种

    原因:使用send方法时,需要注意两点

    1.需要先将生成器挂起,此时才有意义

    2.send方法第一次使用时,也是需要进行一次next()方法执行,或者send(None)执行

    第2条件是在我们使用send前有其他yield语句返回时,可以了解到

    def rep(val):
            yield val
            new = (yield val)
            if new:
                print(new)
            new = (yield val)
            if new:
                print(new)
    
    r = rep(33)
    v = next(r)  #挂起生成器
    print(v)
    v = next(r)  #激活send方法
    print(v)
    r.send("dsad")
    r.send("dsadds")
    ---------------- 正常输出 #
    33 #33 #dsad #dsadds
    若是只是挂起了生成器,没有激活send方法,那么默认第一个send方法会拿去激活
    r = rep(33) v = next(r) print(v) # v = next(r)  #没有去激活send方法 # print(v) r.send("dsad")  #第一个send方法会被用到去激活send r.send("dsadds")    #这个才是正常的信息传入
    ----------

     #33
     #dsadds

    所以我们最好使用send(None)表示去激活send方法,不易混淆

    def rep(val):
            yield val
            new = (yield val)
            if new:
                print(new)
            new = (yield val)
            if new:
                print(new)
    
    
    
    r = rep(33)
    
    v = next(r)    #挂起生成器         也可以用send(None)去挂起生成器,但是还是不要这样做,两个套用容易混淆
    print(v)
    
    r.send(None)    #激活send方法(在首次使用send时使用)
    
    r.send("dsad")
    r.send("dsadds")
    --------------

     #33
     #dsad
     #dsadds

    另外补充下send方法会获取到yield表达式中的返回值

    def rep(val):
            yield val
            new = (yield val)
            if new:
                print(new)
            new = (yield val)
            if new:
                print(new)
    
    
    
    r = rep(33)
    v = next(r)
    print(v)
    
    v = r.send(None)
    print(v,1)
    v = r.send("dsad")
    print(v,2)
    v = r.send("dsadds")  #在最后一个send方法时,没有返回值
    print(v,3)
    -----------------------
    33
    33 1
    dsad
    33 2
    dsadds

    send方法会依次获取yield表达式的返回值,所以在第三个send方法使用时,并没有yield与之对应,所以没有值。

    具体原因暂不讨论。

    注意区分yield语句和yield表达式

    生成器的定义是:包含yield语句的函数是生成器

    再进一步讨论:send方法

     1 def rep(val):
     2         yield val  #这里已经含有yield语句,此函数是生成器,我们在下面执行的next()只是正常执行这条语句
     3         print("t1")
     4         new = (yield val)
     5         print("t2")
     6         if new:
     7             print("t3")
     8             print(new)
     9         new = (yield val)
    10         if new:
    11             print(new)
    12 r = rep(33)

    (1):正常执行yield语句(行2),返回值,并且冻结到行2,不在向下执行

    v = next(r)
    print(v)
    -------
    #33

    那么如何执行到下面的yield表达式

    (2)这时就需要一条语句,去联系yield语句和yield表达式

    v = next(r)
    print(v)
    
    v = r.send(None)  
    print(v,1)
    ---------------------
    33

    t1 33 1
    由上面的两个结果可以看出send(None)执行的语句是
    yield val
    print("t1")
    new = (yield val)
    这两条描红语句,由上面的yield语句,执行到下面yield表达式中的返回值语句中,所以send(None)的返回值就是这里的yield返回的值

    (3)下面的send方法执行一致

    v = next(r)
    print(v)
    
    v = r.send(None)
    print(v,1)
    v = r.send("dsad")
    print(v,2)
    
    ----------------------
    33
    t1
    33 1

    由上面(2)的yield表达式开始(原来只是执行到返回值),现在开始赋值(send("dasd"))传递进去,然后执行到下一条yield返回值语句 t2 t3 dsad
    ------------------------
    new = (yield val)  从这里赋值开始,到下面返回值结束
    print("t2")
    if new:
    print("t3")
    print(new)
    new = (yield val)

     (4)最后一步执行

    v = next(r)
    print(v)
    
    v = r.send(None)
    print(v,1)
    v = r.send("dsad")
    print(v,2)
    v = r.send("dsadds")
    print(v,3)
    
    --------------------------
    33
    t1
    33 1
    t2
    t3
    dsad
    33 2
    dsadds
    --------------------------
    new = (yield val)
    if new:
    print(new)
    这里语句中不在含有返回值,所以我们最后无法接收到值

    当前面没有yield语句时,执行也是相似的

    def rep(val):
            # yield val
            print("t1")      #先执行print('t1')和yield val
            new = (yield val)   #然后执行赋值和向下执行到下一个yield 返回值语句
            print("t2")
            if new:
                print("t3")
                print(new)
            new = (yield val)  #执行赋值,并且将下面语句执行完成*无返回值
            if new:
                print(new)
    
    -----------------------

    r = rep(33)

    v = r.send(None)
    print(v,1)
    v = r.send("dsad")
    print(v,2)
    v = r.send("dsadds")
    print(v,3)    #由于无返回值,不会去执行
    -----------------------
    t1 33 1 t2 t3 dsad 33 2 dsadds

    可以看为:

    若前面没有yield语句,则需要进行生成器挂起。(使用next或者send(None)),然后再使用send执行,方法相似

    若有yield语句,就已经是生成器,我们只需要正常执行他(使用next或者send(None)),然后需要再次使用去激活send方法(连接yield语句和yield表达式中的返回值语句).....

    案例:生成器实现文件流

    #文件输入流
    def FileInputStream(filename):
        try:
            f = open(filename,"r")
            for line in f:
                for byte in line:  #按字节获取数据
                    yield byte
        except Exception as e:
            print(repr(e))  #正常读取文件无错误
        finally:
            f.close()
            return
    
    #文件输出流 def FileOutputStream(inputStream,filename):
    try: f = open(filename,"w") while True: byte = next(inputStream) #若是在调用next方法时,迭代器没有值可以返回,就会引发一个StopIteration错误 f.write(byte) except StopIteration as e: print(repr(e)) #StopIteration() f.close() return FileOutputStream(FileInputStream('f'),"t2")
  • 相关阅读:
    计蒜客 奇怪的国家
    计蒜客 泥塑课
    计蒜客 判断质数
    hiho #1143 : 骨牌覆盖问题·一 (运用快速幂矩阵)
    二叉树建立,先序、中序、后序遍历(c实现)
    hiho #1272 买零食 [Offer收割]编程练习赛2
    hiho #1283 hiho密码 [Offer收割]编程练习赛3
    hiho #1288 微软2016.4校招笔试题 Font Size
    hiho一下 第九十八周 搜索一·24点
    hiho一下 第九十七周 数论六·模线性方程组
  • 原文地址:https://www.cnblogs.com/ssyfj/p/8969299.html
Copyright © 2020-2023  润新知