6.Python3 迭代器与生成器

Python3 迭代器与生成器

1.迭代器

迭代器

迭代器是一个带状态的对象,迭代器内部持有一个状态,该状态用于记录当前迭代所在位置,以便于下次迭代的时候获取正确的元素。迭代器可以通过next()方法来迭代获取下一个值。迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能单向遍历,不能反向遍历。

  • 字面意思:更新迭代,器:工具:可更新迭代的工具。
  • 专业角度:内部含有'__iter__'方法并且含有'__next__'方法的对象就是迭代器。
  • 可以判断是否是迭代器:'__iter__' and '__next__' 在不在dir(对象)

当我们遇到可迭代对象但想将它当作迭代器使用时可以使用iter()方法转化。

li_=iter(li)
printtype(li_))
#输出为“list_iterator",即已经转换为迭代器。

可迭代对象

可迭代对象在进行迭代访问时,不会记忆之前的访问状态,每次都从头开始遍历。

  • 字面意思:可以进行循环更新的一个实实在在值。
  • 专业角度: 内部含有'__iter__'方法的对象,可迭代对象。
  • 判断一个对象是不是可迭代对象: '__iter__' in dir(对象)

迭代器和可迭代对象的区别

  • 如果定义类时,有__iter__方法,那么这个类创建出来的对象一定是可迭代对象。
  • 迭代器有两个基本的方法:iter()next(),一个实现了__iter__方法和__next__方法的对象,就是迭代器。
  • 迭代器对象 一定是 可迭代对象;可迭代对象 不一定是 迭代器;因此迭代器中包括可迭代对象没有的属性和方法,比如__next__()。

字符串,列表或元组对象(可迭代对象)都可用于创建迭代器(转换为迭代器):

list=[1,2,3,4]
it = iter(list)    # iter()获取一个可迭代对象的迭代器
print (next(it))   # next()获取迭代器的数据
# 输出 1 2

# 迭代器对象可以使用常规for语句进行遍历:
list=[1,2,3,4]
it = iter(list)    # 创建迭代器对象
for x in it:
    print (x, end=" ")
# 输出 1 2 3 4

创建一个迭代器(自定义迭代器)

把一个类作为一个迭代器使用需要在类中实现两个方法 iter() 与 next() 。iter() 方法返回一个特殊的迭代器对象, 这个迭代器对象实现了 next() 方法并通过 StopIteration 异常标识迭代的完成。next() 方法会返回下一个迭代器对象。

创建一个返回数字的迭代器,初始值为 1,逐步递增 1,StopIteration 异常用于标识迭代的完成,防止出现无限循环的情况,在 next() 方法中我们可以设置在完成指定循环次数后触发 StopIteration 异常来结束迭代。

class MyNumbers:
  def __iter__(self):
    self.a = 1
    return self
 
  def __next__(self):
    if self.a <= 5:
      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
4
5

2.生成器

在 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
  1. yield ayield 是 Python 中一个非常有用的关键字,它用于定义生成器函数并返回生成器对象。当生成器函数执行到 yield 关键字时,它将将当前函数状态保存为暂停状态,并向调用方返回一个值 a,之后程序流程将被挂起,直到下次通过 next() 函数调用该生成器对象时再恢复执行状态。在斐波那契数列生成器函数中,每次执行到 yield a 时,都会生成当前数列的第一个数字 a 并返回给调用方。
  2. a, b = b, a + b: 这是 Python 中的一种元组赋值语法,可以同时将多个变量赋值为多个值。在斐波那契数列生成器函数中,这行代码的作用是更新相邻两个数字 ab 的值,以便生成下一个斐波那契数。首先,变量 a 被赋值为变量 b 的值,表示将上一个斐波那契数列中的第二个数字赋值为下一个数列的第一个数字;然后,变量 b 被赋值为表达式 a + b 的值,表示将上一个斐波那契数列中的前两个数字相加得到下一个数列中的第二个数字。这样,在每次执行 yield a 之前,都会先更新 ab 的值,从而生成下一个斐波那契数。

示例一

一个函数 f,f 返回一个 list,这个 list 是动态计算出来的(不管是数学上的计算还是逻辑上的读取格式化),并且这个 list 会很大(无论是固定很大还是随着输入参数的增大而增大),这个时候,我们希望每次调用这个函数并使用迭代器进行循环的时候一个一个的得到每个 list 元素而不是直接得到一个完整的 list 来节省内存,这个时候 yield 就很有用。

以斐波那契函数为例,我们一般希望从 n 返回一个 n 个数的 list:

def fab(max): 
   n, a, b = 0, 0, 1 
   L = [] 
   while n < max: 
       L.append(b) 
       a, b = b, a + b 
       n = n + 1 
   return L

上面那个 fab 函数从参数 max 返回一个有 max 个元素的 list,当这个 max 很大的时候,会非常的占用内存。

一般我们使用的时候都是这个样子的,比如:

f = iter(fab(1000))
while True:
    try:
        print (next(f), end=" ")
    except StopIteration:
        sys.exit()

这样我们实际上是先生成了一个 1000 个元素的 list:f,然后我们再去使用这个 f。

现在,我们换一个方法:

因为我们实际使用的是 list 的遍历,也就是 list 的迭代器。那么我们可以让这个函数 fab 每次只返回一个迭代器——一个计算结果,而不是一个完整的 list:

def fab(max): 
    n, a, b = 0, 0, 1 
    while n < max: 
        yield b 
        # print b 
        a, b = b, a + b 
        n = n + 1 

这样,我们每次调用fab函数,比如这样:

for x in fab(1000):
    print(x)

或者 next 函数之类的,实际上的运行方式是每次的调用都在 yield 处中断并返回一个结果,然后再次调用的时候再恢复中断继续运行。

3.迭代器与生成器区别

迭代器和生成器算是 Python 一大特色,其核心是基于迭代器协议来的。

而平时我们经常使用的 for in 循环体,本质就是迭代器协议的一大应用。

同时 Python 内置的集合类型(字符、列表、元组、字典)都已经实现了迭代器协议,所以才能使用 for in 语句进行迭代遍历。for in 循环体在遇到 StopIteration 异常时,便终止迭代和遍历。

再说下可迭代、迭代器、生成器三个概念的联系和区别。

1、可迭代概念范围最大,生成器和迭代器肯定都可迭代,但可迭代不一定都是迭代器和生成器,比如上面说到的内置集合类数据类型。

2、迭代器,迭代器特点是,均可以使用 for in 和 next 逐一遍历。

3、生成器使用yield语句来产生值,而迭代器使用__next__()方法来返回值。

4、生成器按需生成数据,只在需要时才产生值,因此可以节省内存空间。而迭代器需要一次性生成所有数据,可能会占用较多的内存。

5、生成器是通过函数来创建的,而迭代器是通过实现迭代器协议的类来创建的。

6、生成器是可迭代的,可以使用for循环或者next()函数来遍历生成器对象。而迭代器本身就是可迭代的,可以直接使用for循环来遍历迭代器对象。

7、生成器是一种特殊的迭代器,它通过函数来创建,并且可以按需生成数据。而迭代器是一个实现了迭代器协议的对象,它需要一次性生成所有数据。生成器相比于迭代器更加灵活和高效,特别适用于处理大量数据或者需要延迟生成数据的场景。

4.for…in… 循环的本质

1.先调用 iter()函数,它会自动调用可迭代对象中的 __iter__方法,此方法返回这个可迭代对象的 迭代器对象

2.对获取到的迭代器不断调用 next()函数,它会自动调用迭代器中的 __next__方法来获取下一个值

3.当遇到 StopIteration异常后循环结束

4.for 循环可以迭代任何可迭代对象。

posted @   littlecamel  阅读(11)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示