10-Python之路---迭代器和生成器

前言

假如现在有一个列表L = [1,2,3,4,5],如果需要获取列表里的内容,有几种方法?

第一种:

L[0]

第二种:

for i in L:
	print(i)

如果用索引取值,我们可以取到任意位置的值,前提是你得知道这个值在什么位置。

如果用for循环来取值,我们把每个值都取到,不需要关心每个值的位置,因为只能按照顺序来取值。

但是,有没有人想过为啥可以使用for循环取值?

Python中的for循环

想搞清楚for循环,还是得从代码的角度看:

for i in [1,2,3,4,5]:  
    print(i)

上面这段代码没啥问题,但是我们换一种情况,循环一组数字12345,

for i in 12345:  
    print(i)
    
# 结果:
Traceback (most recent call last):
  File "D:/Mark/学员问题.py", line 4, in <module>
    for i in 12345:
TypeError: 'int' object is not iterable

报错了!莫慌,有报错提示TypeError: 'int' object is not iterable,说int类型不是iterable,老马百度翻译了一下

也就是说int类型不是可迭代的!

迭代

什么是迭代

"可迭代"这个概念,我们可以从报错上分析,之所以12345不可以for循环,是因为它不可迭代,反过来意思就是,如果"可迭代",那么就可以被for循环!

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

from collections import Iterable

l = [1, 2, 3, 4]
t = (1, 2, 3, 4)
d = {1: 2, 3: 4}
s = {1, 2, 3, 4}

print(isinstance(l, Iterable))
print(isinstance(t, Iterable))
print(isinstance(d, Iterable))
print(isinstance(s, Iterable))

打印结果都是True,那么迭代指的就是可以将数据一个挨着一个的取出来。

可迭代协议

能被for循环的就是可迭代的,但是for怎么知道谁是可迭代的呢?

假设我们有一个数据类型,希望这个类型里的东西可以被for循环,那么就必须满足for循环的要求,而这个要求就是"协议"

可以满足被迭代的要求就叫做可迭代协议可迭代协议的定义非常简单,就是内部实现了__ iter__方法。

print(dir([1,2]))
print(dir((2,3)))
print(dir({1:2}))
print(dir({1,2}))

在打印结果中,不难发现,都有一个__ iter__方法。也就是说要想可迭代,那么内部必须有一个__iter__方法

那么,__ iter__方法有什么用呢?

print([1,2].__iter__())
# 结果:
<list_iterator object at 0x000001D5CF34C898>

又出现一个不懂的单词iterator,翻译一下

迭代器

又是一个新问题,什么是迭代器?

虽然现在还不清楚,但是我们已经有了一个迭代器,我们来研究下这个列表的迭代器和列表的区别.

# 迭代器的方法 - 列表的方法
print(set(dir([1,2].__iter__()))-set(dir([1,2])))
# 结果:
{'__next__', '__length_hint__', '__setstate__'}

可以看到,迭代器中多了三个方法,那么这三个都分别有什么用处?

iter_list = [1,2,3,4,5,6].__iter__()
#获取迭代器中元素的长度
print(iter_list.__length_hint__())
#根据索引值指定从哪里开始迭代
print(iter_list.__setstate__(4))
#一个一个的取值
print(iter_list.__next__())
print(iter_list.__next__())

在for循环中,就是在内部调用了__next__方法才能取到一个一个的值。

如果我们一直取next取到迭代器里已经没有元素了,就会抛出一个异常StopIteration,告诉我们,列表中已经没有有效的元素了。

L = [1,2,3,4]
L_iter = L.__iter__()
while True:
    try:
        item = L_iter.__next__()
        print(item)
    except StopIteration:
        break

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

动手试试:看看range()是个啥?

生成器

在 Python 中,使用了 yield 的函数被称为生成器(generator)。

跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。

在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。

调用一个生成器函数,返回的是一个迭代器对象。

Python中提供的生成器:

1.生成器函数:常规函数定义,但是,使用yield语句而不是return语句返回结果。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行

2.生成器表达式:类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表

生成器Generator:

  本质:迭代器(所以自带了__iter__方法和__next__方法,不需要我们去实现)

  特点:惰性运算,开发者自定义

​ 好处:节省内存。

栗子:

假如我去饭店吃面,我上来就要挑战100碗,老板娘答应了,然后开始下面给我吃【手动狗头】,她做一碗,我吃一碗,也可以做3碗,我吃3碗,而不是说等100碗全部做好,在给我,等她做完,面坨了,我也不想吃了(要节约粮食!!!)

def produce():
    """下面"""
    for i in range(2000000):
        yield "做好第%s份面"%i

        
product_g = produce()
print(product_g.__next__()) #要一碗
print(product_g.__next__()) #再要一碗
print(product_g.__next__()) #再要一碗

num = 0
for i in product_g:         #要五碗
    print(i)
    num +=1
    if num == 5:
        break

生成器表达式

l1=[i for i in range(10)]   #列表解析
g1=(i for i in range(10))   #生成器表达式
print(g1)
print(next(g1))     #next本质就是调用__next__
print(g1.__next__())
print(next(g1))

1.把列表解析的[]换成()得到的就是生成器表达式

2.列表解析与生成器表达式都是一种便利的编程方式,只不过生成器表达式更节省内存

3.Python不但使用迭代器协议,让for循环变得更加通用。大部分内置函数,也是使用迭代器协议访问对象的。

面试题

# 面试一
def demo():
    for i in range(4):
        yield i

g=demo()

g1=(i for i in g)
g2=(i for i in g1)

print(list(g1))
print(list(g2))
# 面试二
def add(n,i):
    return n+i

def test():
    for i in range(4):
        yield i

g=test()
for n in [1,10]:
    g=(add(n,i) for i in g)

print(list(g))
原文地址:https://www.cnblogs.com/mark-wq/p/13562280.html