什么是 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 i 

When 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() 实施。

阅读更多

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注