一、迭代器
Python中的for循环
我们知道,对于列表、元祖、字典、集合、字符串、range()、文件句柄…都可以使用 for 循环从里面依次取值出来,下面看一个不能 for 循环的例子。
for i in 9527:
print(i)
Traceback (most recent call last):
File "C:/python文件都在这里/python/迭代器.py", line 1, in <module>
for i in 9527:
TypeError: 'int' object is not iterable
我们可以看到报错信息显示的是:TypeError: ‘int’ object is not iterable .也就是说整型是不可以迭代的,那为什么列表元祖之类的可以使用 for 循环呢 ? 难道它们是可迭代的??
迭代和可迭代协议
- 什么叫迭代
从上面的报错信息知道,之所以 9527 不能被 for循环,好像是因为他是“不可迭代的”。那么,如果是“可迭代的”,那应该就可以被 for 循环了。
How to prove?
from collections import Iterable
lis = [1, 2,3,4]
tup = (1,2,3,4)
dic = {1:3,2:4}
set1 = {1,2,3,4}
print(isinstance(lis,Iterable)) # 判断列表是否可迭代
print(isinstance(tup,Iterable))
print(isinstance(dic,Iterable))
print(isinstance(set1,Iterable))
就像我们使用 for 循环取值的现象,将某个数据集里面的数据“一个挨一个地”取出来,就叫做迭代。
- 可迭代协议
如果某个对象要求可迭代,那么它必须满足可迭代协议。
可迭代协议的内容为:内部实现了 __iter__方法。
lis = [1, 2, 3, 4]
tup = (1, 2, 3, 4)
dic = {1: 3, 2: 4}
set1 = {1, 2, 3, 4}
# 使用dir()方法可以获得对象所含的方法
print(dir(lis))
print(dir(tup))
print(dir(dic))
print(dir(set1))
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index']
['__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']
['__and__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__iand__', '__init__', '__init_subclass__', '__ior__', '__isub__', '__iter__', '__ixor__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__or__', '__rand__', '__reduce__', '__reduce_ex__', '__repr__', '__ror__', '__rsub__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__xor__', 'add', 'clear', 'copy', 'difference', 'difference_update', 'discard', 'intersection', 'intersection_update', 'isdisjoint', 'issubset', 'issuperset', 'pop', 'remove', 'symmetric_difference', 'symmetric_difference_update', 'union', 'update']
可以看到以上对象内部均包含__iter__方法。
下面看一下__iter__方法到底完成了什么事情呢?下面我们尝试调用一下它
print([1, 2 ,3].__iter__())
# 结果
<list_iterator object at 0x00000182ECF174A8>
输出结果告诉我们,这是一个 list_iterator, 也就是说是一个列表的迭代器。
下面稍微总结一下
- 能被 for 循环的都是可迭代的,可迭代必须满足可迭代协议,也就是说它的内部实现了 __iter__方法。
- 列表.__iter__()将会返回一个列表的迭代器。(其他的同理…)
迭代器协议
迭代器?what???
要回答这个问题,我们可以从已有的一个列表迭代器出发,查看一下它内部实现的方法,看下和普通的列表的方法有何不同。
lis_dir = dir([1, 2, 3])
lis_itertor_dir = dir([1, 2, 3].__iter__())
print(set(lis_itertor_dir) - set(lis_dir)) # 查看列表迭代器比普通列表多出来的方法
# {'__next__', '__length_hint__', '__setstate__'}
我们看到列表迭代器中多多出了三个方法,那么这三个方法到底实现了什么样的功能呢?
list_itertor = [1,2,3,4].__iter__()
print("length:%d" % list_itertor.__length_hint__()) # 获取迭代器中元素的个数
list_itertor.__setstate__(2) # 根据索引值指定从哪里开始迭代 (从第三个开始迭代)
print("first value:%s" % list_itertor.__next__()) # 一个一个地取值
print("second value:%s" % list_itertor.__next__())
#结果
length:4 # 元素个数
first value:3 # [1,2,3,4][2]
second value:4 # [1,2,3,4][3]
这三个方法中,__next__方法最为强大,实际上,for循环中,就是调用了__next__方法,才取得了一个一个的值。
下面使用__next__方式实现一个 for 循环
ist_itertor = [1, 2, 3, 4].__iter__()
print(ist_itertor.__next__())
print(ist_itertor.__next__())
print(ist_itertor.__next__())
print(ist_itertor.__next__())
print(ist_itertor.__next__()) # 列表里已经没有元素了,再调用 __next__()会引发 StopIteration 错误
# 结果
1
2
3
4
Traceback (most recent call last):
File "C:/python文件都在这里/python 全栈/day9-20/day13/迭代器.py", line 56, in <module>
print(ist_itertor.__next__())
StopIteration
加入异常捕获,改进代码如下
ist_itertor = [1, 2, 3, 4].__iter__()
while True:
try:
item = ist_itertor.__next__()
print(item)
except StopIteration:
break
以上的代码中,我们使用调用ist_itertor的__next__()方法来实现元素一个一个地取值,而ist_itertor就是一个迭代器。
迭代器协议: 内部实现了 __iter__和__next__方法。
我们知道range()是可迭代的,因为它可以被 for 循环,那它是不是一个迭代器呢 ??
from collections import Iterator
print('__iter__' in dir(range(10))) # True
print('__next__' in dir(range(10))) # False
print(isinstance(range(10), Iterator)) # False
可以发现,range()只是可迭代的,但它不是迭代器。
from collections import Iterable
from collections import Iterator
class Jack:
def __iter__(self):
pass
def __next__(self):
pass
a = Jack()
print(isinstance(a, Iterator)) # True
print(isinstance(a, Iterable)) # True
内部既有 __iter__方法又有 __next__方法,所以它是可迭代的,并且是一个迭代器。
from collections import Iterable
from collections import Iterator
class Jack:
def __iter__(self):
pass
a = Jack()
print(isinstance(a, Iterator)) # False
print(isinstance(a, Iterable)) # True
只有__iter__方法,没有__next__方法,所以它只是一个可迭代的对象,但不是一个迭代器。
from collections import Iterable
from collections import Iterator
class Jack:
def __next__(self):
pass
a = Jack()
print(isinstance(a, Iterator)) # False
print(isinstance(a, Iterable)) # False
无__iter__,故不是可迭代的,必然也没法是迭代器。
迭代器的好处
-
可以从容器数据类型中一个一个取值,所有的值都能取到,但是只能取一次
-
迭代器不会在内存中再占用一大块内存, 而是随着循环每次生成一个 next, 就取到一个值。
二、生成器
我们知道迭代器的好处是可以用来节省内存,在某些情况下,我们也需要节省内存,就只能自己写。我们自己写的这个能实现迭代器功能的东西就叫生成器。
本质:迭代器
特点:惰性运算,开发者自定义
Python 中的迭代器
- 生成器函数
在 Python 中,使用了 yield 的函数被称为生成器(generator)函数。
跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。
调用一个生成器函数,返回的是一个迭代器对象。 - 生成器表达式
类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表
生成器函数
一个包含yield关键字的函数就是一个生成器函数,调用生成器函数就会得到一个生成器对象。yield可以像return一样返回函数中的值,但是与return所不同的是,函数中执行了return就意味着函数的结束,但是yield不会,而是将返回值返回后将函数挂起,以便下次在它离开的地方重新开始。
def genertor():
print(1)
yield 1111
print(1)
yield 2222
print(3)
yield 3333
my_g = genertor() # 得到一个生成器对象
print("my_g:%s" % my_g)
print(my_g.__next__())
print(my_g.__next__())
print(my_g.__next__())
# my_g:<generator object genertor at 0x000002882EA1D9A8>
# 1
# 1111
# 1
# 2222
# 3
# 3333
生成器的好处:节省内存,下面 look an example.
def get_number():
for num in range(500000000):
yield num
my_num_g = get_number() # 得到一个生成器
print(my_num_g.__next__()) # 获取第一个值
print(next(my_num_g)) # 获取第二个值
count = 0
for i in my_num_g: # 循环获取接下来的100个并做一些处理
count += 1
print("获取100的第%d个:%d" % (count, i))
if count > 100:
break
- 生成器监听文件输入的例子
def get_file_input(file_name):
f = open(file_name, 'r', encoding='utf-8')
f.seek(0, 2) # 从文件末尾算起
while True:
new_line = f.readline().strip()
if new_line: # 改行有内容并且不只是只有 ‘\n’
yield new_line
my_file_g = get_file_input('user_input') # 获得一个生成器
for each_content in my_file_g:
if 'jacky' in each_content: # 筛选内容中有jacky的
print("find you:%s" % each_content)
- send
def test_send_generator():
print(1)
arg1 = yield 1111
print("content: %s" % arg1)
print(2)
arg2 = yield 2222
print("arg2: %s" % arg2)
yield "end"
my_g = test_send_generator()
ret1 = my_g.__next__()
print("ret1:%s" % ret1)
ret2 = my_g.send("哈哈哈哈")
print("ret2:%s" % ret2)
my_g.send("嗯嗯嗯嗯")
# 运行结果
1
ret1:1111
content: 哈哈哈哈
2
ret2:2222
arg2: 嗯嗯嗯嗯
使用send的注意事项
- send 获取下一个值的效果和next基本一致 只是在获取下一个值的时候,给上一yield的位置传递一个数据
- 第一次使用生成器的时候 是用next获取下一个值
- 最后一个yield不能接受外部的值
计算移动平均值
def get_averager():
"""动态获取平均值"""
count = 0
total = 0.0
average = None # 第一次返回的时候返回的
while True:
new_num = yield average
total += new_num # 获取总值
count += 1
average = total/count # 重新计算平均值
aver_g = get_averager()
next(aver_g) # 返回None,将它丢弃即可
print(aver_g.send(10))
print(aver_g.send(20))
print(aver_g.send(30))
改进:使用装饰器预激活
def init(my_func):
def inner(*args, **kwargs):
to_return_generator = my_func(*args, **kwargs)
next(to_return_generator) # 执行一句next()执行到第一个yield的地方
return to_return_generator
return inner
@init
def get_averager():
"""动态获取平均值"""
count = 0
total = 0.0
average = None # 第一次返回的时候返回的
while True:
new_num = yield average
total += new_num # 获取总值
count += 1
average = total/count # 重新计算平均值
aver_g = get_averager()
# next(aver_g) # 返回None,将它丢弃即可
print(aver_g.send(10))
print(aver_g.send(20))
print(aver_g.send(30))
yield from
yield from是 python 3.3 新出的句法
- 作用一 :替代内存for循环
如果生成器需要产出另一个生成器的值,传统的方法是使用嵌套的for循环
def chain(*iterables):
for it in iterables:
for i in it:
yield i
s = 'Jacky'
t = tuple(range(3))
print(list(chain(s, t))) # ['J', 'a', 'c', 'k', 'y', 0, 1, 2]
chain 生成器把操作依次交给接收到的各个可迭代对象处理。
def chain(*iterables):
for i in iterables:
yield from i
s = 'Jacky'
t = tuple(range(3))
print(list(chain(s, t))) # ['J', 'a', 'c', 'k', 'y', 0, 1, 2]
- yield from 完全代替了内层的 for 循环。
- yield from x 表达式对 x 对象所做的第一件事是,调用 iter(x),从中获取迭代器。因
此,x 可以是任何可迭代的对象。
上面的例子比较简单,因为里面只有一层的for 循环,下面再看一个例子。
将 一个嵌套型的序列 扁平化处理为一列单独的值。
from collections import Iterable
def flatten(items, ignore_types=(str, bytes)):
for x in items:
if isinstance(x, Iterable) and not isinstance(x, ignore_types):
yield from flatten(x)
else:
yield x
items = [1, 2, (1, 33), [3, 4, [5, 6, "hh"], 7], 8]
print(list(flatten(items)))
for x in flatten(items):
print(x, end=" ")
# output
[1, 2, 1, 33, 3, 4, 5, 6, 'hh', 7, 8]
1 2 1 33 3 4 5 6 hh 7 8
- 作用二:打开双通道(略)
从Python 3.5开始引入了新的语法 async 和 await ,而await替代的就是yield from(为了不与实现内层for循环的yield from误解)
参考:
https://www.cnblogs.com/Eva-J/articles/7213953.html
https://www.jianshu.com/p/87da832730f5