zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    Java对象的生命周期与作用域的讨论(转)
    [置顶] Oracle学习路线与方法
    Java实现 蓝桥杯 算法训练 未名湖边的烦恼
    Java实现 蓝桥杯 算法训练 未名湖边的烦恼
    Java实现 蓝桥杯 算法训练 未名湖边的烦恼
    Java实现 蓝桥杯 算法训练 最大的算式
    Java实现 蓝桥杯 算法训练 最大的算式
    Java实现 蓝桥杯 算法训练 最大的算式
    Java实现 蓝桥杯 算法训练 最大的算式
    Java实现 蓝桥杯 算法训练 最大的算式
  • 原文地址:https://www.cnblogs.com/sticker0726/p/7778598.html
Copyright © 2011-2022 走看看