Python--迭代器与生成器
-
迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。
-
两个基本方法。iter()和 next()
-
iter()创建迭代器对象 next()返回迭代器的下一个元素,当迭代器已经是最后一个元素时,如果再用next()会抛出异常StopIteration
-
迭代器也可以被迭代,当被for in 迭代时不会报异常,会自动到异常时停止。
-
迭代器会随着源对象的变化而变化
迭代器 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