python 迭代器和生成器

一、迭代器
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, 也就是说是一个列表的迭代器

下面稍微总结一下

  1. 能被 for 循环的都是可迭代的,可迭代必须满足可迭代协议,也就是说它的内部实现了 __iter__方法。
  2. 列表.__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的注意事项

  1. send 获取下一个值的效果和next基本一致 只是在获取下一个值的时候,给上一yield的位置传递一个数据
  2. 第一次使用生成器的时候 是用next获取下一个值
  3. 最后一个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]
  1. yield from 完全代替了内层的 for 循环。
  2. 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值