• python基础6 迭代器 生成器


    迭代器

    可迭代的或迭代对象

    可迭代的:内部含有__iter__方法的数据类型叫可迭代的,也叫迭代对象  , range是一个迭代对象,内部含有iter()方法。为什么可迭代对象能被for 循环,因为可迭代对象含有iter方法,只要函数iter方法的对象就可以被for循环。这也是可迭代协议。

    运用dir()方法来测试一个数据类型是不是可迭代的的。如果含有iter的,就是可迭代对象、

    迭代器协议

    迭代器协议是指:对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么就引起一个StopIteration异常,以终止迭代 (只能往后走不能往前退)。

    迭代器:可以被next()函数调用并不断返回下一个值的对象称为迭代器,迭代器是一个实现了迭代器协议的对象。也可以这样说包含next方法的可迭代对象叫迭代器

    迭代器和列表的区别?

    区别在于节省内存和惰性运算

    如果我有一个列表中有500个数据,那么这个列表就非常占用内存,如果把列表变为迭代器,就占用很少的内存,为什么会占用内存少呢,因为迭代器只记录当前这元素和下一个元素,当你找我要的时候我才在内存中生成数据,不找我要,就不生成数据,也就不占用内存,这就是迭代器的惰性运算。

    如何区分迭代器和可迭代对象?这个在我们时间长了后会混淆。

    可迭代对象最简单的定义:可以使用for in 语句进行循环的对象。比如字符串、列表、元组、字典以及迭代器、生成器都是可迭代对象。而迭代器是可以使用next()进行回调的对象,迭代器比可迭代对象多一个__next__方法。可迭代对象和迭代器的联系是:可以对迭代对象使用iter()方法来生成迭代器。

    判断一个变量是不是迭代器或者可迭代对象?

    from collections import Iterator
    from collections import  Iterable
    print(isinstance([1,2,3,4],Iterable))
    str_iter="abc".__iter__()
    print(isinstance(str_iter,Iterator))

    结果:

    True
    True

    注意!!!文件enumerate含有next和iter方法所以是迭代器,例如:

     enumerate范例:

    from collections import Iterator
    l=[1,3,4]
    print(isinstance(enumerate(l),Iterator))

    结果:

    True

     文件范例:

    with open("产品",encoding="utf-8") as f:
        print("__next__" in dir(f))

    结果:

    True

    迭代器的特点:

    1.节省内存

    2.惰性运算(什么时候用到,什么时候运行)

    3.从前到后一次取值,过程不可逆,不可重复。

    如果把迭代对象转变为迭代器?

    可迭代对象调用自己的__iter__().方法就会返回一个迭代器

    迭代器=iter(迭代对象)  注意:iter(迭代对象)=迭代对象.__iter__()                    next 和它的用法一样

     迭代器的方法:

    1.__next__方法:返回迭代器的下一个 元素。

    2.__iter__方法:返回迭代器对象本身。

    l=["ha","hei","he"]
    ret=l.__iter__()       #这个步骤生成迭代器,ret就称为了一个迭代器。
    
    print(ret.__next__())
    print(ret.__next__())

    迭代器比可迭代对象多一个__next__方法

    包含__next__方法的可迭代对象就是迭代器

     for循环原理

    1.先判断对象是不是可迭代对象,如果不是直接报错,如果是的话,调用__iter__()返回一个迭代器。
    2.然后不断的调用生成的迭代器的next()方法,每次返回一个值。
    3.迭代到最后,没有更多元素了,就抛出异常 StopIteration,这个异常 python 自己会处理,不会暴露给开发者

    这也就是用for循环取值节省内存的原因。

    模拟for循环,解释for循环的内部原理。

    不加try ....except 的情况下:

    范例一:

    l=[1,2,3,4,5]
    ite=l.__iter__()
    while True:
    print(ite.__next__())
    结果:
    1 2 3 4 5 StopIteration 出现这种原因是while 循环不可以自动停止,然而迭代器从前到后一次取值,
    过程不可逆,不可重复,所以就出现了这种情况。

    如何避免这种错误?

    范例二:

    l=[1,2,3,4,5]
    ite=l.__iter__()
    while True:
        try:
            print(ite.__next__())
        except StopIteration:
                break

    结果:

    1
    2
    3
    4
    5

    总结:for循环是让我们更简单的使用迭代器,用迭代器取值不需要关心索引或者key.

    我们来完整的看看迭代过程是怎么实现的:当任何可迭代对象传入到for循环或其他迭代工具中进行遍历时,迭代工具都是先通过iter函数获得与可迭代对象对应的迭代器,然后再对迭代器调用next函数,不断的依次获取元素,并在捕捉到StopIteration异常时确定完成迭代,这就是完整的迭代过程。这也称之为“迭代协议”。

    作者:酱油哥
    链接:https://www.zhihu.com/question/20829330/answer/286837159

    生成器

    为什么要有生成器?

    迭代器是从集合中取数据,而生成器是创造数据,这点从斐波那契中就可以看出区别来了,我们知道斐波那契数是无穷的,在集合中放不下,那么我们如何生成所有的斐波那契呢,这就用到了生成器

    例如

    def f():
        a=0
        b=1
        while True:
            yield a
            yield b
            a = b + a
            b=b+a
    
    for i in f():
        print(i)

    含有有yield 的函数被称之为生成器(generator)函数。

    生成器函数执行后会得到一个生成器(generator)

    生成器本质:生成器是迭代器,它包含一切迭代器的方法。

    生成器的作用是

    • 延迟计算一次只产生一个数据项.
    • 增加代码的可阅读性

    yield关键字

    yield的作用: 

    1. 记住上次执行的状态
    2. 自动切换到不同任务
    3. 1次只返回一个结果, 把返回值传递给next() 的调用方

    调用生成器send方法传递数据时,必须先调用next(g)或者g.send(None)方法,执行到yield语句,等待接收数据。否则会报错。 

    def func():
        a = yield 5  # send中的值先找到上次暂停的位置,然后把yield 5 替换成world 这里就变成了 a="world
        print("a>>>", a)
        yield 22
    
    
    g = func()
    num = g.__next__()
    print("num>>>",num)
    foo = g.send('world')  # send相当于next(),但是他又和next又有不同,他可以传值给上次暂停的地方,但是send第一次不能先执行,必须先执行next()或者send(none)
    print('foo>>', foo)

    结果:

    num>>> 5
    a>>> world
    foo>> 22

     实现yield 的切换任务的例子,即实现协程的例子

    import time
    def consumer():
        r = "开始吃包子了~"
        while True:
            x = yield r     #r发给send的调用方,x 接收send的传的值
            print("我正在吃包子%s"%(x))
            r = "包子已经收到"
            time.sleep(1)
    
    
    def producer(c):
        p = c.__next__()
        print(p)
        n = 0
        while n < 5:
            n = n + 1
            print("生产者生产包子%s" % n)
            nn = c.send(n)
            print("send****")
            print("收到消费者的消息为%s"%nn)
        c.close()
    
    con = consumer()
    producer(con)

    结果:

    开始吃包子了~
    生产者生产包子1
    我正在吃包子1
    
    send****
    收到消费者的消息为包子已经收到
    生产者生产包子2
    我正在吃包子2
    
    send****
    收到消费者的消息为包子已经收到
    生产者生产包子3
    我正在吃包子3
    
    send****
    收到消费者的消息为包子已经收到
    生产者生产包子4
    我正在吃包子4
    
    send****
    收到消费者的消息为包子已经收到
    生产者生产包子5
    我正在吃包子5
    
    send****
    收到消费者的消息为包子已经收到

    yield能在两个任务之间保存状态和切换,实现了并发的效果.,但是这种并发没有什么意义,只有遇到io阻塞时切换并发才有意义

    yield from  关键字

    yield from 是在python3.3中出现的新语法.

    如果一个生成器需要另一个生成器的值时,传统的方法就是用for循环,yield from就可以带起for循环

    yield from 的主要功能是打开双向通道,把最外层的调用方与最内层的子生成器连接起
    来,这样二者可以直接发送和产出值,还可以直接传入异常,而不用在位于中间的协程中
    添加大量处理异常的样板代码

    传统方式 for循环

    def generator1():
        item = range(5)
        for i in item:
            yield i
    
    def generator2():
        yield 'a'
        yield 'b'
        yield 'c'
        for i in  generator1(): 
            yield i
        yield  'd'
    for i in generator2() :
        print(i)

    使用yield from 语法

    def generator1():
        item = range(5)
        for i in item:
            yield i
    
    def generator2():
        yield 'a'
        yield 'b'
        yield 'c'
        yield from generator1() #yield from iterable本质上等于 for item in iterable: yield item的缩写版
        yield  'd'
    for i in generator2() :
        print(i)

     结果:

    a
    b
    c
    0
    1
    2
    3
    4
    d

    一个题目:

    a="AB"

    b="CD"

    想要生成: A  B  C  D

    方法一:

    def func():
        a="AB"
        b="CD"
        for i in a:
            yield i
    
        for i in b :
            yield i
    
    f=func()
    for i in f:
        print(i)

    方法二

    def func():
        a="AB"
        b="CD"
        yield from a
        yield from  b
    f=func()
    for i in f:
        print(i)

    yield from 调用生成器

    def htest():
        i = 1
        while i < 4:
            n = yield i
            print("nnn",n)
            if i == 3:
                return 100
            i += 1
    
    def itest():
        val = yield from htest() #调用生成器接收,yield from可以接收return的返回值
        print("val>>>",val)
    
    t = itest()
    t.send(None)
    j = 0
    while j < 3:
        j += 1
        try:
            print('j',j)
            t.send(j) #当t.send(3)时,htest函数早已经return了结束了
        except StopIteration as e:
            print('异常了')
    j 1
    nnn 1
    j 2
    nnn 2
    j 3
    nnn 3
    val>>> 100
    异常了

    创建生成器的两种方法

    1.生成器函数。常规函数定义,但是使用yield语句而不是return语句返回结果,yield返回值并不会终止程序的运行,一次只返回一个结果,在每个结果中间它会记住函数状态,以便下次从它离开的地方开始。

    2.生成器表达式:

    要介绍生成器表达式前,要先介绍列表生成式,即列表推导式,说白了就是如何生成一个列表

    常规的写法
    
    egg_list=[]
    for i in range(10):
        egg_list.append('鸡蛋%s' %i)
    
    
    如果用列表推导式可以这么写 语法规则: for 左边是列表 中要存放的结果,for 右边是生成这个列表所需的条件.
    
    ret=['鸡蛋%s'%i for i in range(10)]
    print  (ret)  


    优点:方便,改变了编程习惯,可称之为声明式编程

    结果:

    ['鸡蛋0', '鸡蛋1', '鸡蛋2', '鸡蛋3', '鸡蛋4', '鸡蛋5', '鸡蛋6', '鸡蛋7', '鸡蛋8', '鸡蛋9']


    #1、把列表推导式的[]换成()就是生成器表达式
    
    #2、示例:生一筐鸡蛋变成给你一只老母鸡,用的时候就下蛋,这也是生成器的特性
    >>> chicken=('鸡蛋%s' %i for i in range(5))
    >>> chicken
    <generator object <genexpr> at 0x10143f200>
    >>> next(chicken)
    '鸡蛋0'
    >>> list(chicken) #因chicken可迭代,因而可以转成列表
    ['鸡蛋1', '鸡蛋2', '鸡蛋3', '鸡蛋4',]
    
    #3、优点:省内存,一次只产生一个值在内存中

    既然有了列表生成式,为什么还出现生成器生成式来生成列表,这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。

    生成器函数和普通函数之间的区别?

    1.生成器函数中有yield关键字。

    2.生成器函数执行后不会立即执行,而是返回一个生成器。

    def func():
        print("你好")
        yield 1
        print("中国")
        yield 2
    func()
    print(func())

    结果:

    <generator object func at 0x000002DD23F698E0>    #直接执行生成器函数后形成了一个生成器

    如何让它打印执行?

    def func():
        print("你好")
        yield 1
        print("中国")
        yield 2
    func()
    g=func()
    print(g.__next__()) #生成器就是迭代器。
    print(g.__next__())

    结果:

    你好
    1
    中国
    2
  • 相关阅读:
    c# 三层结构的简单理解
    浅析C#鼠标右键如何添加
    三层结构开发的理解
    Windows快捷键大全
    command 中 ExecuteScalar() ExecuteNonQuery ()和ExecuteReader()的用法
    很拽的JSON
    FCKeditor的全局API
    WebForm_DoPostBackWithOptions 丢失的解决
    闭包的错误例子
    XPath学习笔记 XPath数据模型
  • 原文地址:https://www.cnblogs.com/sticker0726/p/7778598.html
Copyright © 2020-2023  润新知