迭代器
可迭代(iterable)对象:实现了方法 __iter__
的对象是可迭代的。
比如,写一个类,不实现__iter__
方法,用for
循环尝试进行迭代,会提示不是可迭代对象。
判断是否为可迭代对象或迭代器对象,均返回False
from collections.abc import Iterator, Iterable, Generator
class Fib(object):
def __init__(self):
print('__init__被调用')
self.a = 0
self.b = 1
# def __next__(self):
# print('__next__被调用')
# self.a, self.b = self.b, self.a + self.b
# if self.a > 50:
# raise StopIteration
# return self.a
#
# def __iter__(self):
# print('__iter__被调用')
# return self
fib = Fib()
print('被测对象是否为可迭代对象: ', isinstance(fib, Iterable))
print('被测对象是否为迭代器对象: ', isinstance(fib, Iterator))
for i in fib:
print(i)
执行结果:
"D:\Program Files\Python37\python.exe" E:/Pycharm-Project/test.py
__init__被调用
被测对象是否为可迭代对象: False
被测对象是否为迭代器对象: False
Traceback (most recent call last):
File "E:/Pycharm-Project/test.py", line 25, in <module>
for i in fib:
TypeError: 'Fib' object is not iterable
Process finished with exit code 1
再加上__iter__
方法,就可以用for
循环。才疏学浅,暂不清楚怎么创建一个可以for
循环取值又不用__next__
的可迭代对象,只能看着它报错。
检查类型,已经是可迭代对象,但不是迭代器对象
from collections.abc import Iterator, Iterable, Generator
class Fib(object):
def __init__(self):
print('__init__被调用')
self.a = 0
self.b = 1
# def __next__(self):
# print('__next__被调用')
# self.a, self.b = self.b, self.a + self.b
# if self.a > 50:
# raise StopIteration
# return self.a
def __iter__(self):
print('__iter__被调用')
return self
fib = Fib()
print('被测对象是否为可迭代对象: ', isinstance(fib, Iterable))
print('被测对象是否为迭代器对象: ', isinstance(fib, Iterator))
for i in fib:
print(i)
执行结果:
"D:\Program Files\Python37\python.exe" E:/Pycharm-Project/test.py
__init__被调用
被测对象是否为可迭代对象: True
被测对象是否为迭代器对象: False
__iter__被调用
Traceback (most recent call last):
File "E:/Pycharm-Project/test.py", line 25, in <module>
for i in fib:
TypeError: iter() returned non-iterator of type 'Fib'
Process finished with exit code 1
迭代器(Iterator)对象:
1、实现了__next__
方法
2、实现了__iter__
方法,本方法只能return self
from collections.abc import Iterator, Iterable, Generator
class Fib(object):
def __init__(self):
print('__init__被调用')
self.a = 0
self.b = 1
def __next__(self):
print('__next__被调用')
self.a, self.b = self.b, self.a + self.b
if self.a > 50:
print('迭代中止,利用StopIteration退出')
raise StopIteration
return self.a
def __iter__(self):
print('__iter__被调用')
return self
fib = Fib()
print('被测对象是否为可迭代对象: ', isinstance(fib, Iterable))
print('被测对象是否为迭代器对象: ', isinstance(fib, Iterator))
for i in fib:
print(i)
执行结果:
"D:\Program Files\Python37\python.exe" E:/Pycharm-Project/test.py
__init__被调用
被测对象是否为可迭代对象: True
被测对象是否为迭代器对象: True
__iter__被调用
__next__被调用
1
__next__被调用
1
__next__被调用
2
__next__被调用
3
__next__被调用
5
__next__被调用
8
__next__被调用
13
__next__被调用
21
__next__被调用
34
__next__被调用
迭代中止,利用StopIteration退出
Process finished with exit code 0
由执行结果可以看出原理:
fib = Fib()
,首先调用__init__
初始化一个对象;
for
循环会调用fib()
的__iter__
,使其为一个迭代器对象;
循环调用__next__
,遇到StopIteration错误后结束循环。
列表字典等可迭代对象
像list
,set
,dict
,tuple
,str
都是可迭代对象
但是他们都不是迭代器对象,因为没有实现__next__
方法
如果用next()
操作一个列表会提示不是迭代器对象
lista = ['a', 'b', 'c', 'd']
print(next(lista))
Traceback (most recent call last):
File "G:/Study/Code_github/Studyfor/test.py", line 2, in <module>
print(next(lista))
TypeError: 'list' object is not an iterator
迭代器对象是可以用next()
操作的,因为next()
本质上是调用了迭代器对象的__next__
方法
from collections.abc import Iterator, Iterable, Generator
class Fib(object):
def __init__(self):
print('__init__被调用')
self.a = 0
self.b = 1
def __next__(self):
print('__next__被调用')
self.a, self.b = self.b, self.a + self.b
if self.a > 50:
print('迭代中止,利用StopIteration退出')
raise StopIteration
return self.a
def __iter__(self):
print('__iter__被调用')
return self
fib = Fib()
print('被测对象是否为可迭代对象: ', isinstance(fib, Iterable))
print('被测对象是否为迭代器对象: ', isinstance(fib, Iterator))
print(next(fib))
print(next(fib))
print(fib.__next__())
执行结果:
"D:\Program Files\Python37\python.exe" E:/Pycharm-Project/test.py
__init__被调用
被测对象是否为可迭代对象: True
被测对象是否为迭代器对象: True
__next__被调用
1
__next__被调用
1
__next__被调用
2
Process finished with exit code 0
优缺点
优点
-
取值不依赖索引
-
节省内存
比如需要一个十亿位的斐波拉契数列,如果使用列表,这个列表暂用的内存会很大,但是使用迭代器,每次只需要一位的内存占用即可。
缺点
-
只能往后取,不能往前取
迭代器只有
__next__
,元素取完之后,再也无法找回 -
无法预测长度
迭代器是不知道前面有多少,不知道后面有多少,所以无法预测长度。
生成器
生成器是一种特殊的迭代器,生成器有两种,生成器函数和生成器表达式
生成器函数
生成器函数的创建比较简单,用yield
替代return
进行返回就是一个生成器函数。
from collections.abc import Iterator, Iterable, Generator
def my_func():
print('before yield 1')
yield 1
print('after yield 1')
print('before yield 2')
yield 2
print('after yield 2')
print('before yield 3')
yield 3
print('after yield 3')
yield 4
obj = my_func()
print(obj)
print(f'被测对象是否为生成器对象: {isinstance(obj, Generator)}. \n')
a1 = next(obj)
print('main run %s \n' % a1)
a2 = next(obj)
print('main run %s \n' % a2)
a3 = next(obj)
print('main run %s \n' % a3)
执行结果:
"D:\Program Files\Python37\python.exe" G:/Study/Code_github/Studyfor/test.py
<generator object my_func at 0x0000021E1919EC48>
被测对象是否为生成器对象: True.
before yield 1
main run 1
after yield 1
before yield 2
main run 2
after yield 2
before yield 3
main run 3
Process finished with exit code 0
从上面的例子来看。
带有
yield
的函数,返回的obj
是一个生成器(Generator)对象。
a1
的函数执行和主流程yield
就结束了。
在a2
的函数执行部分,才继续执行a1
终止位置的yield
后面的部分,直到第二个yield
结束。
斐波拉契数列表示
from collections.abc import Iterator, Iterable, Generator
def fib():
a = 0
b = 1
while True:
a, b = b, a+b
if b > 30:
raise StopIteration
yield b
obj = fib()
print(obj) # <generator object fib at 0x000001C0AD91EC48>
print(f'被测对象是否为生成器对象: {isinstance(obj, Generator)}. \n') # 被测对象是否为生成器对象: True.
a1 = next(obj)
print('main run %s \n' % a1) # main run 1
a2 = next(obj)
print('main run %s \n' % a2) # main run 2
a3 = next(obj)
print('main run %s \n' % a3) # main run 3
send()方法
生成器函数除了恢复执行的next()
方法,还有一个往里传值并恢复执行的send()
方法。
先看一下官方文档:
generator.send(value)
恢复执行并向生成器函数“发送”一个值。 value 参数将成为当前 yield 表达式的结果。
send()方法会返回生成器所产生的下一个值,或者如果生成器没有产生下一个值就退出则会引发 StopIteration。
当调用 send() 来启动生成器时,它必须以 None 作为调用参数,因为这时没有可以接收值的 yield 表达式。
然后再看实际的例子:
def my_func():
print('未到"yield"语句时,没有传值')
a1 = yield 1
print(f'my_func收到传入值a1: "{a1}"')
a2 = yield 2
print(f'my_func收到传入值a2: "{a2}"')
yield 3
obj = my_func()
b0 = obj.send(None)
print('main run %s \n' % b0)
b1 = obj.send('one')
print('main run %s \n' % b1)
b2 = obj.send('two')
print('main run %s \n' % b2)
执行结果:
"D:\Program Files\Python37\python.exe" G:/Study/Code_github/Studyfor/test.py
未到"yield"语句时,没有传值
main run 1
my_func收到传入值a1: "one"
main run 2
my_func收到传入值a2: "two"
main run 3
Process finished with exit code 0
结合官方文档,看上面的例子。
b0
所在的send
部分用来启动生成器,以None
作为参数。同时b0
处的send
也有着恢复执行的作用,执行到yield 1
。
b1
所在的send
部分,传入一个one
,赋值给a1
,再执行yield 2
结束,返回后给b1
。
从这个例子来理解yield
对应的赋值,a1 = yield 1
其实应该分为两部分a1 = yield
和yield 1
。实际代码执行顺序是:
print('未到"yield"语句时,没有传值')
yield 1 # b0的send()在这里结束
a1 = yield # b1的send()在这里开始
print(f'my_func收到传入值a1: "{a1}"')
但是,这只是为了更好的理解a1 = yield 1
的执行顺序,同时理解为什么启动生成器的send()
需要传入一个None
,因为启动生成器的send()
传进去的值没有变量可以接收。
实际使用不能拆开写,因为按照生成器遇到yield
必停的原则,拆开得停止两次了。
可以看下如果启动生成器的send()
传入不是None
的报错信息:
Traceback (most recent call last):
File "G:/Study/Code_github/Studyfor/test.py", line 17, in <module>
b1 = obj.send('one')
TypeError: can't send non-None value to a just-started generator
生成器表达式
先看比较熟悉的列表的表达式:list_expr = [i for i in range(10)]
生成器表达式就是换一下最外层的括号:generator_expr = (i for i in range(10))
但是,生成器表达式只能是表达式,不能是具体的元素:tuple_expr = (1, 2, 3)
其实也好理解,生成器是一种迭代器,是不知道前,也不知道后的。如果有了具体的元素,那就什么都知道了。
list_expr = [i for i in range(10)]
generator_expr = (i for i in range(10))
tuple_expr = (1, 2, 3)
tuple_expr2 = tuple((i for i in range(10)))
print(type(list_expr))
print(type(generator_expr))
print(type(tuple_expr))
print(type(tuple_expr2))
执行结果:
"D:\Program Files\Python37\python.exe" G:/Study/Code_github/Studyfor/test.py
<class 'list'>
<class 'generator'>
<class 'tuple'>
<class 'tuple'>
Process finished with exit code 0
具体的元素,那就什么都知道了。
```Python
list_expr = [i for i in range(10)]
generator_expr = (i for i in range(10))
tuple_expr = (1, 2, 3)
tuple_expr2 = tuple((i for i in range(10)))
print(type(list_expr))
print(type(generator_expr))
print(type(tuple_expr))
print(type(tuple_expr2))
执行结果:
"D:\Program Files\Python37\python.exe" G:/Study/Code_github/Studyfor/test.py
<class 'list'>
<class 'generator'>
<class 'tuple'>
<class 'tuple'>
Process finished with exit code 0