如果说 Python 中有一个主题会造成混乱的话,那就是生成器的问题。生成器也是函数,但它们与你我日常使用的普通函数有一些区别。今天,我们将温和地沉浸在生成器的世界中,了解它们是什么,它们与普通函数有什么不同,以及为什么或什么时候我们会使用它们。
普通函数
接下来这一点对于理解生成器 与普通函数的威力至关重要。一个普通的函数 在返回结果之前在内存中生成整个操作序列。我们叫它,它执行一个或一组任务,然后返回函数的输出。一旦 ['return'](https://blog.finxter.com/python-return/ "Return Keyword in Python – A Simple Illustrated Guide")语句被执行后,函数就终止了,内存被清空,所使用的变量和函数被遗忘。
def multiply(num):
total = num * 52
return total
print(multiply(6))
# Result
312
在上面的代码中,multiply() 函数被调用,执行方程,返回结果,一切都结束了。如果我在执行这个函数后对变量'total'调用 打印,就会得到一个错误信息 。这个函数已经完成了它的工作,返回了数据,没有什么可以查询的了。
def multiply(num):
total = num * 52
return total
print(multiply(6))
print(total)
# Result
312
Traceback (most recent call last):
File "C:\Users\David\Desktop\Upwork Platform\Generators\OrdFunction.py", line 8, in <module>
print(total)
NameError: name 'total' is not defined
生成器的定义
然而,如果我们定义一个生成器,它是一个在调用时返回一个对象的函数,然后我们可以通过一次调用一个项目来处理这个对象。为了做到这一点,我们使用几个特定的命令。让我们来看看'[yield](https://blog.finxter.com/yield-keyword-in-python-a-simple-illustrated-guide/ "Yield Keyword in Python – A Simple Illustrated Guide")'和 [next()](https://blog.finxter.com/python-next/ "Python next()").
Yield 和 Next 语句
在 Python 中,yield 是一个语句,它从一个函数中返回数据,但不终止该函数,也不忘记变量。把 yield 想象成有点像一个暂停按钮。它使函数暂停,传递数据,然后等待。当你 "取消暂停 "函数时,它将从它离开的地方继续。
因此,这里是生成器函数和标准函数之间的第一个区别。一个函数要成为生成器,必须至少有一个'yield'语句。可能有不止一个yield 语句,也可能有return 语句。然而,如果没有至少一个yield 语句,它就不是一个生成器。
那么,你如何解除函数的暂停呢?这就是我们需要了解 [next()](https://blog.finxter.com/python-next/ "Python next()")函数。next() 函数就是我们之前说的upause按钮。下面是一些代码,以显示next() ,和yield ,如何工作。
def multiply(num):
mult = num * 52
yield mult
add = mult + 185
yield add
subt = add - 76
yield subt
test = multiply(6)
print(next(test))
print(next(test))
print(next(test))
# Result
312
497
421
在前面的代码中,我们激活了函数multiply() ,并将其分配给一个变量 'test'。然后我们在测试时调用next() ,它在程序中运行,直到到达第一个yield ,然后它向我们提供值 312 ,然后它就等待。当我们用第二个next() ,解除函数的暂停时,它就从它离开的地方开始,所有的信息仍然可用,评估下一个代码,并在第二次产生时暂停,在那里它向我们提供了数值497。第三个也是最后一个next() ,将为我们提供421,也就是subt 所持有的数据。
现在,如果我们调用第四个next() ,即使我们知道没有其他值要返回,会发生什么?
...
print(next(test))
print(next(test))
print(next(test))
print(next(test))
# Result
File "C:\Users\David\Desktop\Upwork Platform\Generators\GeneratorsEx1.py", line 17, in <module>
print(next(test))
StopIteration
312
497
421
返回值的过程是一条单行道;一旦你用完了值,你就会得到 'StopIteration' 异常,Python 将不会返回其他值。
生成器的意义是什么?
现在你明白了我们可以使用 yield 暂停一个函数,同时保留函数中的所有细节,我们可以讨论一下为什么我们要使用生成器。生成器的威力在于它允许我们只在需要时才评估和调用一个值,这使得生成器在迭代或循环一个可迭代对象时非常有用。
让我们来学习一些专业术语--迭代器
当你开始学习生成器时,你遇到的第一个障碍就是像下面这个斜体字的句子,这是一个有经验的编码员对一个想要简单解释 "可迭代 "一词的新编码员的回应。
"一个可迭代的对象,它有一个__iter__方法,可以返回一个迭代器,或者定义了一个__getitem__方法,可以接受从零开始的顺序索引(当索引不再有效时引发IndexError)。所以一个可迭代的对象是一个你可以从中获得一个迭代器的对象。"
是的。清晰如泥。谢谢你这么说。很高兴我问了。
所以为了清楚地理解,我们将从学习四个词开始;迭代、迭代、迭代器和可迭代。
- 迭代:迭代的意思是重复某些东西。因此,迭代就是重复一个过程、任务或指令。迭代是一个动词。
- 迭代:这是你在不断重复某事时进行的过程。迭代是你在迭代时所做的事情。迭代是一个名词。
- 迭代器:在Python中,迭代器是一个应用于数据集合的对象,在迭代过程中,每次会返回一个元素。
- 可迭代:是一个元素的集合。根据定义,它意味着能够被迭代的东西;一个能够一次返回一个元素的对象。Python 中的 list 被认为是可迭代的。
所以总结一下,一个迭代器,在迭代的过程中,通过一个可迭代器进行迭代。
生成器的意义是什么?
现在你明白了,我们可以用yield来暂停一个函数,同时保留函数中的所有细节,我们可以讨论一下为什么我们要使用生成器。生成器的威力在于它允许我们评估一个数据集,并在我们需要的时候才调用一个值,这使得生成器在迭代或循环一个可迭代对象时非常有用。
生成器是一个懒惰的迭代器,这意味着当面对一个广泛的数据集时,生成器不是将整个数据集加载到内存中,而是允许数据集中的每个元素被评估并逐一返回,而且只在调用时返回。以我们遇到的一些数据集的大小,在最坏的情况下,如果我们试图加载整个数据集,就会超过可用的内存;最好的情况是大大降低处理能力。
与函数不同,生成器使用的内存要少得多,因为它一次只评估和生成一个项目。
在循环中使用
我们可以很容易地在for-loop中使用生成器。这是因为for-loops通过在后台使用next() 命令来抽象迭代,而且根据其性质,它们提供了特定的方法来防止触发StopIteration异常。在这个代码块中,我们将用for-loop运行之前的代码。
def multiply(num):
mult = num * 52
yield mult
add = mult + 185
yield add
subt = add - 76
yield subt
for item in multiply(6):
print(item)
# Result
312
497
421
匿名生成器
如果我们需要使用一次函数就忘记它,那么我们可以使用行内表达式来创建它们,而不是正式定义生成器。与作为匿名函数的lambda表达式一样,我们可以创建匿名生成器。这个过程类似于使用单行的列表理解,只是我们没有使用方括号符号,而是使用圆括号。
我们将在下面的代码中创建一个生成器对象,然后使用 [next()](https://blog.finxter.com/python-next/ "Python next()")命令来调用它。
numbers = [1, 3, 5, 7, 9, 2, 4, 6, 8]
result = ((x*6)//2 for x in numbers)
print(result, '\n')
print(next(result))
print(next(result))
print(next(result))
print(next(result))
# Result
<generator object <genexpr> at 0x000001F6C9E7B9E0>
3
9
15
21
注意,你也可以将匿名 生成器传递给函数。
numbers = [1, 3, 5, 7, 9, 2, 4, 6, 8]
print(max((x*6)//2 for x in numbers))
# Result
27
总结
我们使用yield语句实现了这种逐步迭代,它 "暂停 "了生成器函数,直到next() 方法调用后续数据。
生成器只在一个方向上迭代一次;你不能在这个过程中倒退以访问早期的值。一旦一个生成器结束,如果你想重申它,你需要创建一个新的生成器对象。
与普通函数不同,生成器具有很高的内存效率,主要是在使用大数据集的时候,因为它们只在调用时加载和评估单个值。
我们经常在循环中使用生成器,在这种情况下,特定的条件会终止调用,避免了StopIteration 异常。
我们可以使用圆括号在行内创建匿名生成器,在这种情况下,一次性的使用排除了完整的定义。
生成器是一种创建迭代器的简单而简洁的方法,而不是创建一个类并使用__iter__() 和__next__() 方法。
我相信这篇文章对理解什么是生成器、我们在什么地方使用生成器以及它们提供的价值很有帮助。谢谢你的阅读。