Python 迭代器与生成器详解—重点精准打击(二)

请添加图片描述

###本文内容干活满满,防止走丢迷路,记得点赞收藏哦!!!

概述

在使用Python的过程中,经常会和列表/元组/字典(list/tuple/dict)、容器(container)、可迭代对象(iterable)、迭代器(iterator)、生成器(generator)等这些名词打交道,众多的概念掺杂到一起难免会让人一头雾水,这里我们用一张图来展现它们之间的关系。
请添加图片描述
接下来,主要讲解迭代器(iterator)和生成器(generator),在这之前我们先来看下容器(container)和可迭代对象(iterable)。

那么什么是容器呢?
容器是一种把多个元素组织在一起的数据结构,容器中的元素可以逐个迭代获取,可以用 in,not in 关键字判断元素是否包含在容器中。通常这类数据结构把所有的元素存储在内存中(也有一些特例,并不是所有的元素都放在内存,比如迭代器和生成器对象),我们常用的 string、set、list、tuple、dict 都属于容器对象。

# string
>>> 'z' in 'hello'
False
>>> 'z' not in 'hello'
True

# list
>>> 5 in [1, 2, 3]
False
>>> 5 not in [1, 2, 3]
True

# set
>>> 5 in {1, 2, 3}
False
>>> 5 not in {1, 2, 3}
True

# tuple
>>> 1 in (1, 2, 3)
True
>>> 5 in (1, 2, 3)
False

# dict
>>> 1 in {1:'one', 2:'two', 3:'three'}
True
>>> 5 in {1:'one', 2:'two', 3:'three'}
False

尽管大多数容器都提供了某种方式获取其中的每一个元素,但这并不是容器本身提供的能力,而是可迭代对象赋予了容器这种能力,当然并不是所有容器都是可迭代的。

那什么是可迭代对象呢?
可以返回一个迭代器的对象都可以称之为可迭代对象。

>>> x = [1, 2, 3]
>>> a = iter(x)
>>> b = iter(x)
>>> next(a)
1
>>> next(a)
2
>>> next(b)
1
>>> type(x)
<class 'list'>
>>> type(a)
<class 'list_iterator'>
>>> type(b)
<class 'list_iterator'>

这里的 x 是一个可迭代对象,可迭代对象和容器一样是一种通俗的叫法,并不是指某种具体的数据类型,list 是可迭代对象,dict 也是可迭代对象。a 和 b 是两个独立的迭代器,迭代器内部有一个状态,该状态用于记录当前迭代所在的位置,以方便下次迭代时获取正确的元素。 迭代器有一种具体的迭代器类型,比如 list_iterator,set_iterator。可迭代对象实现了 iter() 方法,该方法返回一个迭代器对象。

当运行代码:

x = [1, 2, 3]
for ele in x:
    ...

实际执行情况是:
请添加图片描述在循环遍历自定义容器对象时,会使用python内置函数iter()调用遍历对象的__iter__()获得一个迭代器,之后再循环对这个迭代器使用next()调用迭代器对象的__next__()。iter()只会被调用一次,而__next__()会被调用 n 次。

参考: Python迭代器和生成器详解

Iterator(迭代器)

  • 迭代器是一个可以记住遍历的位置的对象。
  • 迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。
  • 迭代器只能往前不会后退
  • 迭代器有两个基本的方法:iter() 和 next()。
  • 字符串,列表或元组对象都可用于创建迭代器
>>> list=[1,2,3,4]
>>> it = iter(list)    # 创建迭代器对象
>>> print (next(it))   # 输出迭代器的下一个元素
1
>>> print (next(it))
2

迭代器对象可以使用常规for语句进行遍历:

#!/usr/bin/python3
 
list=[1,2,3,4]
it = iter(list)    # 创建迭代器对象
for x in it:
    print (x, end=" ")

输出:

1 2 3 4

也可以使用 next() 函数:

#!/usr/bin/python3
 
import sys         # 引入 sys 模块
 
list=[1,2,3,4]
it = iter(list)    # 创建迭代器对象
 
while True:
    try:
        print (next(it))
    except StopIteration:
        sys.exit()

执行以上程序,输出结果如下:

1
2
3
4

创建一个迭代器
把一个类作为一个迭代器使用需要在类中实现两个方法 iter() 与 next() 。

如果你已经了解的面向对象编程,就知道类都有一个构造函数,Python 的构造函数为 init(), 它会在对象初始化的时候执行。

更多内容查阅:Python3 面向对象

iter() 方法返回一个特殊的迭代器对象, 这个迭代器对象实现了 next() 方法并通过 StopIteration 异常标识迭代的完成。

next() 方法(Python 2 里是 next())会返回下一个迭代器对象。

创建一个返回数字的迭代器,初始值为 1,逐步递增 1:

class MyNumbers:
  def __iter__(self):
    self.a = 1
    return self
 
  def __next__(self):
    x = self.a
    self.a += 1
    return x
 
myclass = MyNumbers()
myiter = iter(myclass)
 
print(next(myiter))
print(next(myiter))
print(next(myiter))
print(next(myiter))
print(next(myiter))

执行输出结果为:

1
2
3
4
5

迭代器是一个带状态的对象,它能在你调用next()方法时返回容器中的下一个值,任何实现了__iter__()和__next__()(Python2.x中实现next())方法的对象都是迭代器,iter()返回迭代器自身,next()返回容器中的下一个值,如果容器中没有更多元素了,则抛出StopIteration异常。

StopIteration
StopIteration 异常用于标识迭代的完成,防止出现无限循环的情况,在 next() 方法中我们可以设置在完成指定循环次数后触发 StopIteration 异常来结束迭代。

在 20 次迭代后停止执行:

class MyNumbers:
  def __iter__(self):
    self.a = 1
    return self
 
  def __next__(self):
    if self.a <= 20:
      x = self.a
      self.a += 1
      return x
    else:
      raise StopIteration
 
myclass = MyNumbers()
myiter = iter(myclass)
 
for x in myiter:
  print(x)

执行输出结果为:

1
2
3
.
.
.
19
20

迭代器与列表的区别:构建迭代器的时候,不像列表把所有元素一次性加载到内存,而是以一种延迟计算(lazy evaluation)方式返回元素,这正是它的优点。比如列表中含有一千万个整数,需要占超过100M的内存,而迭代器只需要几十个字节的空间。因为它并没有把所有元素装载到内存中,而是等到调用next()方法的时候才返回该元素(按需调用 call by need 的方式,本质上 for 循环就是不断地调用迭代器的next()方法)。

itertools模块里的函数返回的都是迭代器对象。为了更直观的感受迭代器内部的执行过程,我们自定义一个迭代器,以斐波那契数列为例:

#-*- coding:utf8 -*-
class Fib(object):
    def __init__(self, max=0):
        super(Fib, self).__init__()
        self.prev = 0
        self.curr = 1
        self.max = max

    def __iter__(self):
        return self

    def __next__(self):
        if self.max > 0:
            self.max -= 1
            # 当前要返回的元素的值
            value = self.curr
            # 下一个要返回的元素的值
            self.curr += self.prev
            # 设置下一个元素的上一个元素的值
            self.prev = value
            return value
        else:
            raise StopIteration

    # 兼容Python2.x
    def next(self):
        return self.__next__()

if __name__ == '__main__':
    fib = Fib(10)
    # 调用next()的过程
    for n in fib:
        print(n)
    # raise StopIteration
    print(next(fib))

斐波那契数列指的是 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 … 这样一个数列,这个数列从第三项开始,每一项都等于前两项之和。

生成器(generator)

在 Python 中,使用了 yield 的函数被称为生成器(generator)。
跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。

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

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

以下实例使用 yield 实现斐波那契数列:

#!/usr/bin/python3
 
import sys
 
def fibonacci(n): # 生成器函数 - 斐波那契
    a, b, counter = 0, 1, 0
    while True:
        if (counter > n): 
            return
        yield a
        a, b = b, a + b
        counter += 1
f = fibonacci(10) # f 是一个迭代器,由生成器返回生成
 
while True:
    try:
        print (next(f), end=" ")
    except StopIteration:
        sys.exit()

执行以上程序,输出结果如下:

0 1 1 2 3 5 8 13 21 34 55

普通函数用return返回一个值,还有一种函数用yield返回值,这种函数叫生成器函数。函数被调用时会返回一个生成器对象。生成器其实是一种特殊的迭代器,不过这种迭代器更加优雅,它不需要像普通迭代器一样实现__iter__()和__next__()方法了,只需要一个yield关键字。生成器一定是迭代器(反之不成立),因此任何生成器也是一种懒加载的模式生成值。
下面来用生成器再来实现斐波那契数列的例子:

#-*- coding:utf8 -*-
def fib(max):
    prev, curr = 0, 1
    while max > 0:
        max -= 1
        yield curr
        prev, curr = curr, prev + curr

if __name__ == '__main__':
    fib = fib(6)
    # 调用next()的过程
    for n in fib:
        print(n)
    # raise StopIteration
    print(next(fib))

上面是生成器函数,再来看下生成器的表达式,生成器表达式是列表推导式的生成器版本,看起来像列表推导式,但是它返回的是一个生成器对象而不是列表对象。

>>> x = (x*x for x in range(10))
>>> type(x)
<class 'generator'>
>>> y = [x*x for x in range(10)]
>>> type(y)
<class 'list'>

我们再看看一个例子:

#-*- coding:utf8 -*-
import time

def func(n):
    for i in range(0, n):
        # yield相当于return,下一次循环从yield的下一行开始
        arg = yield i
        print('func', arg)

if __name__ == '__main__':
    f = func(6)
    while True:
        print('main-next:', next(f))
        print('main-send:', f.send(100))
        time.sleep(1)

运行结果为:

main-next: 0
func 100
main-send: 1
func None
main-next: 2
func 100
main-send: 3
func None
main-next: 4
func 100
main-send: 5
func None
Traceback (most recent call last):
File “demo.py”, line 13, in
print(‘main-next:’, next(f))
StopIteration

yield就是return返回一个值,并且记住这个返回的位置,下次迭代就从这个位置后(下一行)开始。next方法和send方法都可以返回下一个元素,区别在于send可以传递参数给yield表达式,这时传递的参数会作为yield表达式的值,而yield的参数是返回给调用者的值。

生成器优点
Python中是一种很常用也很好用的数据结构,比起列表(list)来说,迭代器最大的优势就是延迟计算,按需使用,而不是立即产生结果,从而提高开发体验和运行效率。以至于在Python 3中map,filter等操作返回的不再是列表而是迭代器,所以,对于读取大文件或者无限集合,最好是使用迭代器。

  • 节省资源消耗,和声明序列不同的是生成器在不使用的时候几乎不占内存,也没有声明计算过程
  • 使用的时候,生成器是随用随生成,用完即刻释放,非常高效
  • 可在单线程下实现并发运算处理效果

总结

  1. 可迭代对象(Iterable)是实现了__iter__()方法的对象,通过调用iter()方法可以获得一个迭代器(Iterator)。
  2. 迭代器(Iterator)是实现了__iter__()方法和__next()__方法的对象。
  3. for…in…的迭代实际是将可迭代对象转换成迭代器,再重复调用next()方法实现的。
  4. 生成器(Generator)是一个特殊的迭代器,它的实现更简单优雅。
  5. yield是生成器实现__next__()方法的关键。它作为生成器执行的暂停恢复点,可以对yield表达式进行赋值,也可以将yield表达式的值返回。

###都看到这了,点赞收藏的都是大佬!!!

参考资源: python 迭代器和生成器
Python3 迭代器与生成器

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
社会演员多的头像社会演员多普通用户
上一篇 2022年5月28日
下一篇 2022年5月28日

相关推荐