什么是 Python 生成器? (实现你自己的 range() 函数)
Python 中的生成器(也称为生成器函数)用于一次创建一系列值。 让我们通过重新创建内置函数来了解它们是如何工作的 range()
功能。 如果您一次生成系列中的所有值,这可以避免您的程序需要大量内存。 例如,在 Python 2 中运行 for i in range(1000000):
这 range()
函数将创建并返回一个包含一百万个整数的列表。 这会占用大量内存,即使 for 循环一次只需要一个整数。 这在 Python 3 中得到修复,其中 range()
现在一次产生一个整数。 生成器函数允许您一次使用任何类型的数据创建一个值,而不仅仅是整数范围。
如果您发现自己创建了大量值列表,例如列表理解或 for
循环,但一次只需要一个值,则应考虑使用生成器。 如果您正在创建包含数十万项或更多项的大型列表,则尤其如此。 使用生成器就像使用 readline()
一次一行读取文本文件的方法,而不是使用 read()
一次读入整个文件的方法。
作为旁注,生成器函数返回生成器对象,生成器对象是可迭代对象。 (可迭代对象超出了这篇博文的范围,但请参阅迭代器协议:“For 循环”如何在 Python 中工作以了解更多详细信息。)
您可以从 whatIsAGenerator.py 下载所有示例的单个 .py 文件。 这篇博文假设您对 Python 有基本的初学者水平的理解。
首先,让我们看一下您已经熟悉的常规函数:
### A regular function: def aRegularFunction(param): print('Hello', param) return 42 print("Calling aRegularFunction('Alice'):") returnedValue = aRegularFunction('Alice') print('Returned value:', returnedValue)
运行此代码时,输出如下所示:
Calling aRegularFunction('Alice'): Hello Alice Returned value: 42
这很熟悉,也不足为奇。 函数为其参数获取参数,运行一些代码,然后返回一些返回值。
现在让我们看一下生成器函数。 你可以告诉一个函数是一个生成器函数,因为它有 yield
函数体某处的关键字:
### A generator (aka generator function): def aGeneratorFunction(param): print('Hello', param) yield 42 print('How are you,', param) yield 99 print('Goodbye', param) return 86 # Raises StopIteration with value 86. print("Calling aGeneratorFunction('Bob'):") generatorObj = aGeneratorFunction('Bob') # Does not run the code, but returns a generator object. print('Calling next():') yieldedValue = next(generatorObj) print('Yielded value:', yieldedValue) print('Calling next():') yieldedValue = next(generatorObj) print('Yielded value:', yieldedValue) print('Calling next():') try: next(generatorObj) except StopIteration as excObj: print('type(excObj) is', type(excObj)) print('excObj.value is', excObj.value) print('type(excObj.value) is', type(excObj.value))
运行此代码时,输出如下所示:
Calling aGeneratorFunction('Bob'): Calling next(): Hello Bob Yielded value: 42 Calling next(): How are you, Bob Yielded value: 99 Calling next(): Goodbye Bob type(excObj) is <class 'StopIteration'> excObj.value is 86 type(excObj.value) is <class 'int'>
您需要知道的是,一个人不会简单地调用生成器函数。 调用生成器函数不会运行它的代码。 相反,调用生成器函数会返回一个生成器对象。 您可以在交互式 shell 中看到:
>>> aGeneratorFunction('Eve') <generator object aGeneratorFunction at 0x0000013470179580>
要在生成器函数中运行代码,您必须调用 Python 的内置函数 next()
函数并将生成器对象传递给它。 这将运行代码直到 yield
声明,它就像一个 return
陈述并使 next()
调用返回一个值。 下一次 next()
被调用时,执行从局部变量的所有相同值的 yield 语句恢复。
当一个 return
语句到达时,生成器函数引发一个 StopIteration
例外。 通常没有返回值,但如果有则设置为异常对象的 value
属性。
这 next()
函数通常不与生成器一起使用,而是生成器函数用于 for
环形。 在每次迭代中, for
loop 将循环变量设置为产生的值。 这 StopIteration
异常告诉 for
循环生成器对象已经耗尽了它的值。 例如:
### Using a generator function with a for loop or list() call: for yieldedValue in aGeneratorFunction('Carol'): print('Yielded value:', yieldedValue) listOfGeneratedValues = list(aGeneratorFunction('David')) print('List of generated values:', listOfGeneratedValues)
运行此代码时,输出如下所示:
Hello Carol Yielded value: 42 How are you, Carol Yielded value: 99 Goodbye Carol Hello David How are you, David Goodbye David List of generated values: [42, 99]
请记住,生成器主要用于生成一系列值,但一次一个。 让我们使用斐波那契数列生成器的示例。 斐波那契数列以 0 和 1 开头,然后数列中的下一个数是前两个数的和:0、1、1、2、3、5、8、13、21、34、55,依此类推在。
生成器一次可以生成这些数字(达到给定的最大值)。 该发生器可与 for
循环或一个 list()
称呼:
### A practical example of a generator using the Fibonacci sequence: def fibonacciSequence(maxNum=50): a = 0 b = 1 yield a yield b while True: a, b = b, b + a if b >= maxNum: return b # Raise StopIteration. else: yield b for fibNum in fibonacciSequence(): print('Next number in the Fibonacci sequence:', fibNum) fibNumsUpTo500 = list(fibonacciSequence(500)) print('List of fib numbers up to 500:', fibNumsUpTo500)
运行此代码时,输出如下所示:
Next number in the Fibonacci sequence: 0 Next number in the Fibonacci sequence: 1 Next number in the Fibonacci sequence: 1 Next number in the Fibonacci sequence: 2 Next number in the Fibonacci sequence: 3 Next number in the Fibonacci sequence: 5 Next number in the Fibonacci sequence: 8 Next number in the Fibonacci sequence: 13 Next number in the Fibonacci sequence: 21 Next number in the Fibonacci sequence: 34 List of fib numbers up to 500: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377]
总而言之,Python 生成器看起来像包含以下内容的函数 yield
关键词。 它们用于一次生成一系列值,这样您的程序就不会因一次生成所有值而消耗大量内存。 调用生成器函数时,它不会运行其中的代码,而是返回一个生成器对象。 这个生成器对象可以传递给内置的 next()
功能。 这会运行生成器函数中的代码,直到 yield
语句(它给出了一个值 next()
返回)或 return
声明(这引发了 StopIteration
表示没有更多的值可以生成)。 然而,发电机通常用于 for
循环,自动处理它们。
就是这样! 生成器并没有那么复杂。 我建议您通过查看 Trey Hunner 的博客文章迭代器协议:“For 循环”如何在 Python 中工作来了解一般的可迭代对象和迭代器。
实现你自己的 range() 函数
让我们通过创建自己的知识来测试我们的知识 range()
功能。 代替 for i in range(10):
,我们将能够使用 for i in mySimpleImplementationOfRange(10):
. 我们将使用一个生成器函数,它接受一个整数参数,并产生从 0
直到但不包括论点:
### Using generator functions to re-implement range(): def mySimpleImplementationOfRange(stop): i = 0 while iWhen you run this code, the output looks like this:
0 1 2 3 4当然,
range()
功能比这复杂一点。 它最多可以使用三个参数来指定起始整数、停止整数和“步进”整数以指示生成的整数应更改多少。 还,range()
只接受整数参数,step 参数不能是0
. 更完整的实现如下:### Using generator functions for a more complete range() re-implementation: def myImplementationOfRange(firstParam, secondParam=None, thirdParam=None): if secondParam is None: if not isinstance(firstParam, int): raise TypeError("'" + firstParam.__class__.__name__ + "' object cannot be interpreted as an integer") start = 0 stop = firstParam step = 1 elif thirdParam is None: if not isinstance(secondParam, int): raise TypeError("'" + secondParam.__class__.__name__ + "' object cannot be interpreted as an integer") start = firstParam stop = secondParam step = 1 else: if not isinstance(thirdParam, int): raise TypeError("'" + thirdParam.__class__.__name__ + "' object cannot be interpreted as an integer") if thirdParam == 0: raise ValueError('myImplementationOfRange() arg 3 must not be zero') start = firstParam stop = secondParam step = thirdParam i = start if step > 0: # step arg is increasing while i stop: yield i i += step # Adding a negative to decrease return i for i in myImplementationOfRange(4, 12, 2): print(i)运行此代码时,输出如下所示:
4 6 8 10还有比这更多的东西,因为
range
实际上是一个实现了几个 dunder 方法的类(你可以从 Nina Zakharenko 的 PyCon 2018 演讲 Elegant Solutions for Everyday Python Problems 中了解到。但是现在你可以看到像 Python 的内置方法range()
实施。