10分钟了解Python黑魔法 Yield、Iterator、Generator
创始人
2025-07-09 15:51:28
0

今天,我们来讨论Python的yield、Iterator和generator,它们可以在许多教程中看到,但总是引起一些混淆。

今天,我们来讨论Python的yield、Iterator和generator,它们可以在许多教程中看到,但总是引起一些混淆。

就像decorators一样,这三个概念是紧密联系在一起的。例如,如果你想知道什么是yield,你必须首先了解什么是generator。但在理解generator之前,你又必须理解iterator是什么,但在理解iterator之前,您必须要知道iterable对象是什么。他们的关系如下图:

Iterables 可迭代的

可迭代是指能够通过迭代的方法遍历的对象,比如列表、字符串、元组、字典、集合等等。简单的例子:

mylist = [1, 2, 3]
for i in mylist:
    print(i)

可迭代对象如何工作?

让我们看看Python解释器在遇到迭代操作时如何处理迭代,例如for ... in x

  • 调用 iter(x) 函数
  • 检查对象是否实现了 _iter__ 方法,如果实现了,则调用它以获取迭代器;
  • 如果未实现 _iter__ 方法,但实现了_getitem__ 方法,Python将创建一个迭代器并尝试按顺序获取元素(从索引0开始);
  • 如果两个方法都未实现,将抛出TypeError异常,指示无法迭代该对象。

因此具有 __iter__ 方法或 __getitem__方法的对象通常称为可迭代对象。

如何判断一个对象是否可迭代?

  • 方法一:使用dir函数,检查对象是否实现了__iter__ 或者 __getitem__方法。
mylist = [1, 2, 3]
mylistMethod = dir(mylist)
print(mylistMethod) #查看mylist的方法
print('__iter__' in dir(mylist) or '__getitem__' in dir(mylist)) # True
  • 方法二:使用isinstance函数,检查对象是否是Iterable类型。
from collections import Iterable
mylist = [1, 2, 3]
print(isinstance(mylist, Iterable)) # True

Iterator 迭代器

迭代器是一个包含可数数量值的对象。它可以迭代,这意味着您可以遍历所有值。让我们看一个迭代器示例:

for i in range(5):
    print(i) # 0 1 2 3 4

像这样,一个个打印元素的过程就叫可迭代的,这个过程也是我们日常代码编写中接触最多的操作。

简单来说,带有next()方法的可迭代对象就是一个迭代器,或者说一个可迭代对象和一个迭代器的关系是:Python从一个可迭代对象中获取一个迭代器。具体关系如下图:

所以上面提到的列表、字符串等不是迭代器。但是,您可以使用Python内置 iter()函数来获取它们的迭代器对象。让我们使用迭代器模式来重写前面的例子:

mylist = [1,2,3]
it = iter(mylist) # 获取迭代器对象
while True:
    try:
        print(next(it))
    except StopIteration:
        print("Stop iteration!")
        break

在上面的代码中,我们首先使用iterable对象mylist来构造迭代器it,并不断调用迭代器上的next()函数来获取下一个元素。如果没有字符,迭代器将抛出 StopIteration 异常并退出循环。

Generator 生成器

Python 提供了一个生成器来创建迭代器函数。生成器是一种特殊类型的函数,它不返回单个值,而是返回一个包含一系列值的迭代器对象。在生成器函数中,使用 yield 语句而不是 return 语句。

现在我们已经知道for循环背后的机制了,但是如果数据量太大,比如for i in range(1000000),使用for循环将所有的值存储在内存中不仅占用大量的存储空间 但是如果我们只需要访问前几个元素,空间就浪费了。在这种情况下,我们可以使用 generator 。

生成器的思路是,我们不需要一次性把这个列表全部创建出来,只需要记住它的创建规则,然后在需要用到的时候,再一次次的计算和创建。我们来看一个例子:

my_generator = (x*x for x in range(10))
for i in my_generator:
    print(i) # 0 1 4 9 16 25 36 49 64 81

my_generator 是一个生成器,它的每一个元素都是一个生成器对象。我们可以使用 next()函数来获取下一个元素。

Yield 产生器

简单来说,你可以把yield当成return,但它返回的是一个生成器。记住,刚开始学习的时候不需要了解这个yield是什么,但是一定要了解它的运行机制!让我们看一下下面的代码片段:

def test():
    print("First")
    yield 1
    print("Second")
    yield 2
    print("Third")
    yield 3
my_generator = test() # 创建生成器
print(type(my_generator)) # 

我们可以在这里看到如果一个函数使用 yield 作为返回值,那么它就变成了一个生成器函数。与普通函数不同,生成器函数被调用后,函数体中的代码不会立即执行(执行my_generator=test()后不打印任何值),而是返回一个生成器!正如我们前面提到的:generator 是迭代器,而 yield 可以被视为 return ,不难猜测下面代码的结果:

def test():
    print("First")
    yield 1
    print("Second")
    yield 2
    print("Third")
    yield 3
for item in test():
    print(item)
# 输出:
"""
First
1
Second
2
Third
3
"""

next 函数是如何运行的?

def test():
    print("First")
    yield 1
    print("Second")
    yield 2
    print("Third")
    yield 3

my_generator = test() # 创建生成器
a = next(my_generator) # First
print(a) # 1
b = next(my_generator) # Second
print(b) # 
c = next(my_generator) # Third
print(c) # 3
d = next(my_generator) # StopIteration
print(d) # error

每次调用next(my_generator),只跑到yield位置就停止,下次再跑,从上次结束的位置开始!并且生成器的长度取决于在函数中定义 yield 的次数。看起来也很好理解呢。

如果理解了上面的 yield 函数示例,让我们继续看一个更复杂的示例,该生成器可以接受参数。

def simple_gen(a):
    print('-> Started: a =', a)
    b = yield a
    print('-> Received: b =', b)
    c = yield a + b
    print('-> Received: c =', c)
gen = simple_gen(14)
next(gen) # -> Started: a = 14
next(gen) # ?
next(gen) # ?

运行结果如图:

发生了什么??从第一次 next(gen) 调用开始,它在 yield a 处停止,然后当您再次调用 next(gen) 时,b 实际上是 None 值,这导致了异常。

b 为什么是 None 值?因为我们在 yield a 处没有接收到任何值,所以 b 就是 None 值。要想接收值,

要继续,您需要使用 send() 函数:生成器发送(值)恢复执行并将值“发送”到生成器函数中。value 参数成为当前 yield 表达式的结果。send() 方法返回生成器生成的下一个值,或者如果生成器退出而没有生成另一个值则引发 StopIteration。

怎么理解send() 函数?一个带参数的 next(),接收参数,执行yield,然后返回值。

def simple_gen(a):
    print('-> Started: a =', a)
    b = yield a
    print('-> Received: b =', b)
    c = yield a + b
    print('-> Received: c =', c)
gen = simple_gen(14)
next(gen) # -> Started: a = 14
gen.send(15) # Received: b = 15 # send 15 to generator,并执行下一步 send包含next的yield

总结

小思考:

  • yield 和 return 的区别,你理解了么?
  • yield, generator  和 iterator 的区别和联系,你理解了么?

相关内容

热门资讯

PHP新手之PHP入门 PHP是一种易于学习和使用的服务器端脚本语言。只需要很少的编程知识你就能使用PHP建立一个真正交互的...
网络中立的未来 网络中立性是什... 《牛津词典》中对“网络中立”的解释是“电信运营商应秉持的一种原则,即不考虑来源地提供所有内容和应用的...
各种千兆交换机的数据接口类型详... 千兆交换机有很多值得学习的地方,这里我们主要介绍各种千兆交换机的数据接口类型,作为局域网的主要连接设...
什么是大数据安全 什么是大数据... 在《为什么需要大数据安全分析》一文中,我们已经阐述了一个重要观点,即:安全要素信息呈现出大数据的特征...
如何允许远程连接到MySQL数... [[277004]]【51CTO.com快译】默认情况下,MySQL服务器仅侦听来自localhos...
如何利用交换机和端口设置来管理... 在网络管理中,总是有些人让管理员头疼。下面我们就将介绍一下一个网管员利用交换机以及端口设置等来进行D...
P2P的自白|我不生产内容,我... 现在一提起P2P,人们就会联想到正在被有关部门“围剿”的互联网理财服务。×租宝事件使得劳...
Intel将Moblin社区控... 本周二,非营利机构Linux基金会宣布,他们将担负起Moblin社区的管理工作,而这之前,Mobli...
施耐德电气数据中心整体解决方案... 近日,全球能效管理专家施耐德电气正式启动大型体验活动“能效中国行——2012卡车巡展”,作为该活动的...
Windows恶意软件20年“... 在Windows的早期年代,病毒游走于系统之间,偶尔删除文件(但被删除的文件几乎都是可恢复的),并弹...