跳到主要内容位置

迭代器和生成器

可迭代对象#

在python中,但凡内部含有__iter__方法的对象,都是可迭代对象。

"""
查看对象内部的方法:
可以通过dir() 去判断一个对象具有什么方法
dir()会返回一个列表,这个列表中含有该对象的以字符串的形式所有方法名。
这样我们就可以判断python中的一个对象是不是可迭代对象了
"""
s = 'jqm'
i = 100
print('__iter__' in dir(s)) # True
print('__iter__' in dir(i)) # False

从字面意思来说:可迭代对象就是一个可以重复取值的实实在在的东西。

从专业角度来说:但凡内部含有__iter__方法的对象,都是可迭代对象。

迭代对象的优点#

  • 可以直观的查看里面的数据。

可迭代对象的缺点#

  1. 占用内存。
  2. 迭代对象不能迭代取值(除去索引,key以外)。通过for循环进行取值,其实for循环在底层做了一个小小的转化,就是先将可迭代对象转化成迭代器,然后在进行取值的。

迭代器#

可迭代对象如何转化成迭代器

l1 = [1, 2, 3, 4, 5, 6]
obj = l1.__iter__() # 或者 iter(l1)
print(obj) # <list_iterator object at 0x000002057FE1A3C8>

迭代器取值:

l1 = [1, 2, 3, ]
obj = l1.__iter__() # 或者 iter(l1)
# print(obj) # <list_iterator object at 0x000002057FE1A3C8>
ret = obj.__next__()
print(ret)
ret = obj.__next__()
print(ret)
ret = obj.__next__()
print(ret)
ret = obj.__next__() # StopIteration
print(ret)
# 迭代器利用next取值:一个next取对应的一个值
# 如果迭代器里面的值取完了,还要next,那么就报StopIteration的错误

while模拟for的内部循环机制:

for循环的内部机制是:将可迭代对象转换成迭代器,然后利用next进行取值,最后利用异常处理处理StopIteration抛出的异常。

l1 = [1, 2, 3, 4, 5, 6]
# 1 将可迭代对象转化成迭代器
obj = iter(l1)
# 2,利用while循环,next进行取值
while 1:
# 3,利用异常处理终止循环
try:
print(next(obj))
except StopIteration:
break

从字面意思来说:迭代器就是可以迭代取值的工具。

从专业角度来说:在python中,内部含有__iter__方法并且含有__next__方法的对象就是迭代器。

迭代器的优点#

  1. 节省内存

    迭代器在内存中相当于只占一个数据的空间:因为每次取值都上一条数据会在内存释放,加载当前的此条数据。

  2. 惰性机制

    next一次,取一个值,绝不过多取值。

迭代器的缺点#

  1. 不能直观的查看里面的数据。
  2. 取值时不走回头路,只能一直向下取值。

可迭代对象和迭代器的对比#

可迭代对象:

  • 是一个私有的方法比较多,操作灵活(比如列表,字典的增删改查,字符串的常用操作方法等),比较直观,但是占用内存,而且不能直接通过循环迭代取值的这么一个数据集。

    应用:当你侧重于对于数据可以灵活处理,并且内存空间足够,将数据集设置为可迭代对象是明确的选择。

迭代器:

  • 是一个非常节省内存,可以记录取值位置,可以直接通过循环+next方法取值,但是不直观,操作方法比较单一的数据集。

    应用:当你的数据量过大,大到足以撑爆你的内存或者你以节省内存为首选因素时,将数据集设置为迭代器是一个不错的选择。(可参考为什么python把文件句柄设置成迭代器)。

生成器#

生成器和迭代器核心基本相同,生成器的本质就是迭代器,唯一的不同就是:

迭代器都是Python给你提供的已经写好的工具或者通过数据转化得来的,(比如文件句柄,iter([1,2,3])。

生成器是需要我们自己用python代码构建的工具。

生成器的本质就是迭代器.迭代器如何取值,生成器就如何取值。所以我们可以直接执行next()来执行以下生成器

def func():
for i in range(1, 5000):
yield f'{i}号包子'
ret = func()
for i in range(2000):
print(next(ret))
for i in range(2000):
print(next(ret))

yield与return的区别:#

  • return一般在函数中只设置一个,他的作用是终止函数,并且给函数的执行者返回值。
  • yield在生成器函数中可设置多个,他并不会终止函数,next会获取对应yield生成的元素。

yield from#

  • 在python3中提供一种可以直接把可迭代对象中的每一个数据作为生成器的结果进行返回

    # 对比yield 与 yield from
    def func():
    lst = ['卫龙','老冰棍','北冰洋','牛羊配']
    yield lst
    g = func()
    print(next(g)) # 只是返回一个列表
    def func():
    lst = ['卫龙','老冰棍','北冰洋','牛羊配']
    yield from lst
    g = func()
    # 他会将这个可迭代对象(列表)的每个元素当成迭代器的每个结果进行返回。
    print(next(g))
    print(next(g))
    print(next(g))
    print(next(g))
    '''
    yield from ['卫龙','老冰棍','北冰洋','牛羊配']
    等同于:
    yield '卫龙'
    yield '老冰棍'
    yield '北冰洋'
    yield '牛羊配'
    '''

生成器表达式#

生成器表达式和列表推导式的语法上一模一样,只是把[]换成()就行了。比如将十以内所有数的平方放到一个生成器表达式中

gen = (i ** 2 for i in range(10))
print(gen) # 结果: <generator object <genexpr> at 0x0000026046CAEBF8>

生成器表达式和列表推导式的区别:#

  1. 列表推导式一目了然,生成器表达式只是一个内存地址。
  2. 得到的值不一样,列表推导式得到的是一个列表.生成器表达式获取的是一个生成器
  3. 列表推导式比较耗内存,所有数据一次性加载到内存。而生成器表达式遵循迭代器协议,逐个产生元素。