ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Python--迭代器与生成器

2022-01-14 09:04:52  阅读:127  来源: 互联网

标签:__ 迭代 Python 生成器 next -- 对象 print


 

  • 迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。

  • 两个基本方法。iter()和 next()

    • iter()创建迭代器对象    next()返回迭代器的下一个元素,当迭代器已经是最后一个元素时,如果再用next()会抛出异常StopIteration

    • 迭代器也可以被迭代,当被for in 迭代时不会报异常,会自动到异常时停止。

    • 迭代器会随着源对象的变化而变化

要想弄清楚迭代器、生成器,首先从概念上就要弄清三个词,Ieterator/Generator/Iterable

迭代器  Ieterator 是一个迭代器类,平时所讲的迭代器其实指的是迭代器对象。

生成器  Generator 是一个生成器类,平时所讲的生成器其实指的是生成器对象。

可迭代类  Iterable 是一个可迭代类。

迭代器类和生成器类 都是 可迭代类的子类,也就是说它们都是可迭代的,它们的对象都是可迭代类的子对象。

可迭代类,例如 数组、字典等常见的可以放入for循环中进行迭代的都是可迭代类的子对象。

除了放入for循环之外,可以放入iter()中的也必须是可迭代的。

生成器类 是 迭代器类 的子类。两者区别不大, 生成器可以看做特殊的迭代器。

其实iter()函数的作用就是,将一个iterable的对象生成iterator(迭代器对象)。

要想成为iterable的对象,最关键的一点是要实现 __iter__()构造函数,这个函数是被迭代时自动调用的,__inter__中可以什么都不写但不能没有,如果写了那它必须返回一个迭代器对象(它自身或者其他)。例如数组、字典类里面就有__iter__()函数,自定义生成器(有yield)虽然看不到此函数,但是其实父类也有实现,会自动调用。

而要想成为iterator对象,也就是 直接成为 迭代器对象,__inter__()和__next__()二者缺一不可

,因为迭代器要能够直接通过next()执行,通俗理解:比如列表本身是可迭代对象,但不是迭代器,不可能直接next([1,2,3])必须通过next(iter([1,2,3]))

系统内置函数iter()在运行时就是去用该对象的__inter__()方法。所以两种方式都可 test.__inter__()或inter(test)

系统内置函数next()在运行时就是去用该对象的__next__()方法。所以两种方式都可 test.__next__()或next(test)

next()的使用和__inter__没有任何关系,任何实现了__next__方法的对象都可以被next()调用。

for循环里面也会先生成被迭代对象的 迭代器 (无论是用它自身的还是父类里实现的__inter__),生成了以后,再依次用__next__()方法顺序迭代,直至结束。

拓展:列表/字典生成的迭代器并不是标准iterator类,而是各自继承的,比如list_iterator

通俗理解--:

可迭代对象指的是某个对象能够被迭代,有被迭代的潜力(实现了__inter__),除此以外并不能做什么,

迭代器就像数据流,就像视图对象,创建后会受到源数据的影响,只能顺序靠next执行,也可

以放入for中。它保存的其实是算法(指的是标准迭代器不是自定义的类对象),特性是惰性计算序列,随时执行随时计算,

正因如此它相当于可以保存无限长度的元素。比如自定义迭代器、生成器然后编写while死循环,就可以一直执行了。

通俗理解2--:迭代器就像根据某个可迭代对象生成的特殊算法+引用(占用空间很少),顾名思义,迭代器,每次执行根据算法计算引用对象的下一个值。对于标准内置的迭代器而言(比如列表/字典生成的)每次next就是取下个值,而对于自定义迭代器(实现了__next__有自己的逻辑)或者yield生成器而言,就是执行一次next里面的逻辑,或者说执行某部分代码。

生成器是迭代器的子类,按产生方式分为简单生成器,自定义生成器(yield)。

简单生成器就是     g= (x for x in range(10))  注意是小括号不是[],此时g就是一个生成器对象generator

简单生成器对象跟迭代器对象没什么区别,都是next执行或者for执行,也是惰性序列,受到源数据影响等等,只是增加了一些自定义计算。

自定义生成器比较常见,用法多样。

自定义生成器: 任何函数中只要用到了  yield  关键字,就会成为生成器,调用的函数就成为了生成器对象。

详情见testGenerator   比如 def custom() 里面用到了yield关键字,那么 aaa = custom() ,此时aaa就是一个生成器对象了。

但是此时aaa 相当于并没有任何执行,这个和普通的函数有区别。

自定义生成器中有几个注意的点:

    yield 关键字之后的值,可以是任何值或者变量或者.. ,每次执行到yield停止时都会把它后面的值返回给next()或者send()

    等任何调用执行的函数,当做返回值。等到下一次执行的时候会从yield左边的位置继续运行。

    通俗来讲,可以把custom这个函数分为可以执行的几段或无数段部分,第一部分是从开始到第一次yield,2是从yield到第二个yield,

    以此类推。next()就是依次执行各部分。send()就是先给yield赋值,然后再next()。

    由于第一部分里面还并没有执行到yield,所以如果一开始就用aaa.send('test')是会报错的,如果想用只能aaa.send(None)。

    结束问题。如果生成器里面的循环没有写死,而是有个结束条件,那么当满足条件结束循环的时候,执行函数会报错StopIteration,

    所以如果有结束条件,要在next()/send()的外面加上异常捕捉。

换句话说就是,如果迭代器已经到最后一个元素了,再用next()会报错。放入for in中迭代不会报错。

如果想要拿到整个函数的返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中。

迭代器的长度:迭代器是不能够正常取长度的,

~.像简单的iter([1,2,3])或者(x+2 for x in range(100))  获取长度两种思路:1.先转为list 再取  len(list(iterator))。这样有点无用操作。2.sum方式  sum(1 for _ in iteration)3.封装一下:

def get_length(generator):     if hasattr(generator,"__len__"):         return len(generator)     else:         return sum(1 for _ in generator)

~.自定义函数逻辑的那种或者无限循环的,只能自定义一个计数变量了。

Python的for循环本质上就是通过不断调用next()函数实现的,例如:

for x in [1, 2, 3, 4, 5]:     pass

完全等价于:

it = iter([1, 2, 3, 4, 5]) # 循环:while True:     try:         x = next(it)     except StopIteration:         # 遇到StopIteration就退出循环         break

 

下面几段代码可以看一下,便于理解。

可以将几个函数写到一个文件中测试,头部一定要引入几个类。

 

#这几个类要先引入一下
from collections.abc import Iterator from collections.abc import Iterable from collections.abc import Generator import time

 

 

 

 

#这是从学习网站上找来的一小段例子,参考用
 
import sys         # 引入 sys 模块
 
list=[1,2,3,4]
it = iter(list)    # 创建迭代器对象
 
while True:
    try:
        print (next(it))
    except StopIteration:
        sys.exit()
class MyNumbers:
    # def __init__(self):
    #     self.a = 88

    def __iter__(self):
        self.a = 1
        #返回什么都可以,但一定要是迭代器类对象或其子对象
        return iter([1,2,3])
        # return self
        # return (i for i in range(100))

    def __next__(self):
        x = self.a
        self.a += 1
        return x

def testIterator():
    myclass = MyNumbers()
    print(isinstance(myclass, Iterable))  # True
    print(issubclass(MyNumbers, Iterable))  # True
    print(isinstance(myclass, Iterator))  # True
    print(issubclass(MyNumbers, Iterator))  # True
    # 创建实例,这里mycalss的类型是 <class '__main__.MyNumbers'> ,但是它是迭代器对象了,相当于iterator的子对象
    #  其实实例后不需要再iter()了,实例本身就是迭代器了,可以直接next(myclass)使用了。

    myiter = iter(myclass)  #生成标准迭代器,返回的值就是__inter__所返回的。

    print(type(myclass))
    print(type(myiter))
    # for x in myiter:
    #     print(x)
    print(myiter.__next__())
    print(myiter.__next__())
    print(next(myiter))
def testIterator3():
# 测试 迭代器iterator、生成器generator、可迭代对象iterable之间的关系
# 测试 迭代器iterator、生成器generator、可迭代对象iterable之间的关系#  iterable > iterator > generator   生成器一定是迭代器一定是可迭代对象,但可迭代对象不一定是xx,比如列表是可迭代对象,它的# 类实现了__inter__方法,但它不是迭代器。
    #一个数组
    arr = [1,2,3,4]
    #生成一个迭代器iterator
    ii = iter(arr)
    #生成一个生成器generator
    gg = (x for x in [5,6,7,8])

    print(type(ii)) #<class 'list_iterator'>
    print(type(gg)) #<class 'generator'>

    print(isinstance(arr,Iterable)) #True,列表是可迭代对象
    print(isinstance(ii,Iterable)) #True,迭代器是可迭代对象
    print(isinstance(gg,Iterable)) #True,生成器是可迭代对象

    print(isinstance(arr,Iterator)) #Flase,列表不是迭代器对象,即 列表不是迭代器
    print(isinstance(ii,Iterator)) #True,迭代器是迭代器对象
    print(isinstance(gg,Iterator)) #True,生成器是迭代器对象
    
    print(issubclass(Iterator,Iterable))    #True,迭代器类是可迭代类的子类
    print(issubclass(Generator,Iterator))    #True,生成器类是迭代器类的子类    
def testIterator2():
#测试嵌套iter迭代,值会不会受初始影响
    arr = [6,7,8,9]
    arrIter = iter(arr)
    arrIter2 = iter(arrIter)  #迭代器可以再次生成迭代器
    arrIter3 = iter(arrIter2)

    arr[2] = 888
    print(arr)    #[6, 7, 888, 9]
    print(arrIter)      #<list_iterator object at 0x00000265B935FEB0>
    print(arrIter2)     #<list_iterator object at 0x00000265B935FEB0>
    print(arrIter3)     #<list_iterator object at 0x00000265B935FEB0>
    print(next(arrIter),next(arrIter),next(arrIter))   # 6 7 888  迭代器的值会受到影响
    print(next(arrIter2))    #  9
    #迭代器重复迭代,返回的值是自身,也就是都引用的同一个对象。类似于变量的引用。
#!/usr/bin/python3
 
import sys
 
def fibonacci(n): # 生成器函数 - 斐波那契
    a, b, counter = 0, 1, 0
    while True:
        if (counter > n): 
            return
        yield a
        a, b = b, a + b
        counter += 1
f = fibonacci(10) # f 是一个迭代器,由生成器返回生成
 
while True:
    try:
        print (next(f), end=" ")
    except StopIteration:
        sys.exit()
def testGenerator():
#通过生产包子 吃包子的例子学习 生成器的并发运用
    def consumer(name):  # 消费者
        print("%s 准备吃包子啦!" % name)
        while True:
            baozi = yield '我是yield之后的,每次运行到yield我都会被返回'
            print("包子【%s】来了,被【%s】吃了!" % (baozi, name))

    def producer():  # 生产者
        c = consumer('A')
        c2 = consumer('B')

        # 此时 c和c2都是 生成器generator了,此时它们相当于都没有进行任何执行呢。
        print(type(c))
        print(type(c2))

        # print(c.__next__())
        print(next(c2))
        time.sleep(3)
        print(next(c2))
        time.sleep(3)
        print(next(c2))

        # print(c.send(None))
        # print(c.send(123))
        # time.sleep(3)
        # print(c.send(456))
        # print(c.send(789))

        print("开始做包子了!")
        for i in ["韭菜馅","茴香馅","鸡蛋馅","猪肉馅"]:
            time.sleep(1.5)
            print("做了两个个包子")
            c.send(i)       #------------------------
            c2.send(i)      # .send(i):先给yield发送值再next

    producer()

 

  • 还有一个点,关于占用内存空间的小测试,借鉴了一位博主的文章。  https://www.cnblogs.com/yinsedeyinse/p/11848287.html  

 

import os
import psutil

def show_memory_info(hint):
    pid = os.getpid()
    p = psutil.Process(pid)

    info = p.memory_full_info()
    memory = info.uss / 1024. /1024
    print('{} memory used:{}MB'.format(hint,memory))

def test_iterator():
    show_memory_info('initing iterator')
    list_1 = [i for i in range(100000000)]
    show_memory_info('after iterator initiated')
    print(sum(list_1))

def test_generator():
    show_memory_info('intiting generator')
    list_2 = (i for i in range(100000000))
    show_memory_info('after generator initiated')
    print(sum(list_2))
    show_memory_info('after sum called')

test_iterator()
test_generator()

initing iterator memory used:7.21875MB
after iterator initiated memory used:1848.28515625MB
4999999950000000
intiting generator memory used:1.7109375MB
after generator initiated memory used:1.7421875MB
4999999950000000
after sum called memory used:2.109375MB

输出
initing iterator memory used:7.21875MB after iterator initiated memory used:1848.28515625MB 4999999950000000 intiting generator memory used:1.7109375MB after generator initiated memory used:1.7421875MB 4999999950000000 after sum called memory used:2.109375MB

 

 

 

标签:__,迭代,Python,生成器,next,--,对象,print
来源: https://www.cnblogs.com/among-y/p/15800424.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有