迭代器和生成器

迭代器(iterator) 

迭代器协议:必须拥有__iter__方法和__next__方法。

字符串,列表,元组,字典,集合都可以被for 循环,说明他们都是可迭代的。

复制代码
from collections import Iterable

l=[1,2,3,4,5]
t=(3,4,5,6,6)
d={'a':1,'b':2}
s={1,2,3,4,5}
print(isinstance(l,Iterable))
print(isinstance(t,Iterable))
print(isinstance(d,Iterable))
print(isinstance(s,Iterable))

# True
# True
# True
# True
复制代码

结合使用for循环时的现象,可以从字面上理解,迭代就是可以将某个数据集内的数据'一个挨着一个的取出来'。

可迭代协议:内部含有__iter__方法的数据类型就是可迭代的

验证可迭代协议:

复制代码
print(dir([1,2]))
print(dir((2,3)))
print(dir({'a':1,'b':2}))
print(dir({1,2,3,4}))

# ['__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']
复制代码

由上可以看出,可以被for循环的都是可迭代的,要想可迭代,内部必须要有一个__iter__方法。

执行以下__iter__方法:

print([1,2].__iter__())

# <list_iterator object at 0x000001A68B0210B8>

得到了一个list_iterator,此时我们又得到了一个新的名词——iterator。

同样的,可以对字典、集合、元祖进行__iter__操作

复制代码
print((1,2,3).__iter__())
print({'a':1,'b':2}.__iter__())
print({1,2,3}.__iter__())

# <tuple_iterator object at 0x0000021688F61080>
# <dict_keyiterator object at 0x0000021688E8A4A8>
# <set_iterator object at 0x0000021688F60630>
复制代码

同样的得到了tuple_iterator、dict_keyiterator、set_iterator,这就说明了它们都是可迭代的。

可以通过集合求差集来看可迭代对象与迭代器质检的差别。

print(set(dir([1,2].__iter__()))-set(dir([1,2])))

# {'__next__', '__length_hint__', '__setstate__'}
#可以看出可迭代对象比迭代器多了一个__next__功能

对可迭代对象进行操作:

复制代码
iter_1=[1,2,3,4,5].__iter__()
print(iter_1.__length_hint__())
# 从索引为2的元素开始迭代
print('*',iter_1.__setstate__(2))
#一个一个的取值
print('**',iter_1.__next__())
print('***',iter_1.__next__())

# 5
# * None
# ** 3
# *** 4
复制代码

可以看到其中的__next__方法可以实现一个一个的取值。在for循环的内部就是调用了__next__方法才能取到一个一个的值。

写一个使用迭代器的__next__方法来遍历的程序,不需要for循环:

复制代码
l=[1,2,3,4]
l_iter=l.__iter__()
item=l_iter.__next__()
print(item)
item=l_iter.__next__()
print(item)
item=l_iter.__next__()
print(item)
item=l_iter.__next__()
print(item)
item=l_iter.__next__()
print(item)

# 1
# 2
# 3
# 4
#   File "G:/群下载/2.闭包的概念.py", line 206, in <module>
#     item=l_iter.__next__()
# StopIteration
复制代码

上述方法确实将l中的每个元素都遍历且取出来了,但是当l中的元素个数不清楚 的时候我们会写很多行重复的代码,且当超出范围的时候还会像上面一样报错。

这个时候,我们需要使用异常处理机制来将这个异常来处理掉。用while循环来模拟for循环的原理:———for循环时依赖迭代器,且也可以仿照写一个。

for 循环就是想让我们更简单的使用迭代器

用迭代器取值就不需要关心索引或者key的问题了。

复制代码
l=[1,2,3,4]
l_iter=l.__iter__()
while True:
    try:
        item=l_iter.__next__()
        print(item)
    except StopIteration:
        break

# 1
# 2
# 3
# 4
复制代码

range()方法:

复制代码
# 查看__iter__方法中range()使用dir()方法之后是否还存在
print('__iter__'in dir(range(12)))
#查看__next__方法中range()使用dir()方法之后是否还存在
print('__next__' in dir(range(12)))
from collections import Iterable
print(isinstance(range(100),Iterable))  # 验证range执行之后得到的结果是不是一个迭代器

# True
# False
# True
复制代码

可迭代的必须要含有__iter__方法  # 可迭代协议

迭代器比可迭代的多一个__next__方法

迭代器:包含__next__,__iter__方法的就是迭代器。  #  迭代器协议

    包含__next__方法的可迭代对象就是迭代器

迭代器是可迭代的一部分

获得迭代器:可迭代的调用__iter__()

使用迭代器:__next__()

如何判断一个变量是不是迭代器或者可迭代的

方法一:

复制代码
print('__iter__' in dir([1,2,3,4]))
print('__next__' in dir([1,2,3,4]))

# True
# False
# 由此可以看出[1,2,3,4]有__iter__用法,没有__next__用法,说明该列表是一个可迭代对象,
#但不是一个迭代器。
复制代码

方法二:

复制代码
from collections import Iterable
print(isinstance(([1,2,3,4]),Iterable))
# True
str_iter='abc'.__iter__()
print(isinstance(str_iter,Iterable))
# True
print(isinstance('abc',Iterable))
# True
复制代码

只是记录当前这个元素和下一个元素

range_iter=range(10).__iter__()
print(range_iter.__next__())
print(range_iter.__next__())
# 0
# 1

总结迭代器的特点:1、惰性运算;

         2、从前到后依次的取值,但是过程是不可逆的,不可重复的;

         3、节省内存

生成器: 

用生成器做监听文件的程序

复制代码
def tail():
    f = open('文件', 'r', encoding='utf-8')  # 打开文件
    f.seek(0, 2)  # 将光标移到文件内容的最后
    while True:
        line = f.readline()  # 读出文件中光标之后的内容
        if line:  # 如果有则输出新加的内容
            yield line
g = tail()
for i in g:
    print(i.strip())
复制代码

send用法:

1、从哪一个yield开始接着执行,就把一个值传给了那个yield

2、send不能用在第一个触发生成器

3、生成器函数中有多少个yield就必须有多少个next+send

复制代码
def func():
    print('*'*10)
    a = yield 5
    print(a)
    b = yield 10
g = func()
num = g.__next__()
print(num)
num2 = g.send('alix')
print(num2)

# **********
# 5
# alix
# 10
复制代码

利用send计算滚动平均值:

复制代码
def avarge():
    total=0.0
    count=0
    avarge=None
    while True:
        trem=yield avarge
        count+=1
        total+=trem
        avarge=total/count
g=avarge()
avg_num=g.__next__()
avg_num=g.send(10)
print(avg_num)
avg_num=g.send(30)
print(avg_num)
avg_num=g.send(60)
print(avg_num)

# 10.0
# 20.0
# 33.333333333333336
复制代码

将上面的滚动平均值改成装饰器的形式:

复制代码
def init(func):  # 生成器的预激装饰器
    def inner(*args,**kwargs):
        g=func(*args,**kwargs)  # func=avarge
        g.__next__()
        return g
    return inner
@init
def avarge():
    total=0.0
    count=0
    avarge=None
    while True:
        trem = yield avarge
        count += 1
        total += trem
        avarge = total/count
g = avarge()
avg_num=g.send(10)
print(avg_num)
avg_num=g.send(30)
print(avg_num)
avg_num=g.send(60)
print(avg_num)

# 10.0
# 20.0
# 33.333333333333336
复制代码
复制代码
# 将a='AB',b='CD'输出,输出成'A','B','C','D'
def func():
    a = 'AB'
    b = 'CD'
    yield from a
    yield from b
g=func()
g_s=g.__next__()
print(g_s)
g_s=g.__next__()
print(g_s)
g_s=g.__next__()
print(g_s)
g_s=g.__next__()
print(g_s)
# A
# B
# C
# D
复制代码

或将上面的代码简化

复制代码
# 将a='AB',b='CD'输出,输出成'A','B','C','D'
def func():
    a = 'AB'
    b = 'CD'
    yield from a
    yield from b
g=func()
for i in g:
    print(i)
# A
# B
# C
# D
复制代码

触发执行的方式:
next和send是执行几次拿几个数据,在取值的过程中不知道到底有多少个,可能会超出范围,当超出范围的时候会报错。

其中send(None)==__next__(),send中next的基础上传一个值到生成器函数内部;send操作不能用在生成器使用的第一次。

for循环每次取一个值,取完为止,不会报错

复制代码
def cloth():
    for i in range(100):
        yield '衣服%s'%i
g = cloth()
for c in g:
    print(c)
    if c.endswith('20'):  # 打印到衣服20
        break
print(g.__next__())  # 再次触发生成器,输出衣服21到衣服99
print('*'*20)
for c in g:
    print(c)
复制代码

列表推导式:

求出l=[1,2,3,4,5,6,7]中每个元素的平方并存入新的列表中

复制代码
#方法一:
l=[1,2,3,4,5,6,7]
li=[]
for i in l:
    li.append(i*i)
l=li
print(l)
# [1, 4, 9, 16, 25, 36, 49]


#方法二:
l=[1,2,3,4,5,6,7]
x=[i*i for i in l]
print(x)
# [1, 4, 9, 16, 25, 36, 49]
复制代码

生成器表达式:

复制代码
l=[1,2,3,4,5,6,7]
g=(i*i for i in l)
for i in g:
    print(i)
# 1
# 4
# 9
# 16
# 25
# 36
# 49
复制代码

使用生成器的优点:
1、延迟计算,一次返回一个结果。也就是说,它不会一次生成所有的结果,这在处理较多数据的时候可以节省内存空间。

2、提高代码可读性

字典的推导式:

找出names中含有2个e的名字:

names=[{'Tom','Billy','Jefferson','Andrew','Wesley','Steven','Joe'},
{'Alice','Jill','Ana','Wendy','Jennifer','Sherry','Eva'}]
复制代码
names=[{'Tom','Billy','Jefferson','Andrew','Wesley','Steven','Joe'},
       {'Alice','Jill','Ana','Wendy','Jennifer','Sherry','Eva'}]
ret=[name for name_list in names for name in name_list if name.count('e')>=2]  # 相当于两个for循环的嵌套,之后再加上一个if判断
print(ret)

# ['Steven', 'Jefferson', 'Wesley', 'Jennifer']
复制代码
原文地址:https://www.cnblogs.com/hzqblog/p/7832762.html