个性化阅读
专注于IT技术分析

python3生成器generator – Python3教程

点击下载

上一章Python教程请查看:python3迭代器iterator

在本文中,你将了解如何使用Python生成器轻松地创建迭代,它与迭代器和普通函数有何不同,以及为什么应该使用它。

Python中的生成器是什么?

在Python中构建迭代器有很多开销,我们必须使用__iter__()和_next__()方法来实现一个类,跟踪内部状态,在没有要返回的值时触发StopIteration等等。

这既冗长又违反直觉,生成器在这种情况下可以派上用场。

Python生成器是创建迭代器的一种简单方法,上面提到的所有开销都由Python中的生成器自动处理。

简单地说,生成器是一个函数,它返回一个对象(迭代器),我们可以迭代该对象(一次一个值)。

如何在Python中创建生成器?

用Python创建生成器相当简单,它与用yield语句而不是return语句定义普通函数一样简单。

如果一个函数包含至少一个yield语句(它可能包含其他yield或return语句),它就成为一个生成器函数,yield和return都将从函数返回一些值。

不同之处在于,当return语句完全终止一个函数时,yield语句会暂停该函数保存其所有状态,然后在后续调用时继续执行。

生成器函数与普通函数的区别

以下是生成器函数与普通函数的区别。

  • 生成器函数包含一个或多个yield语句。
  • 当被调用时,它会返回一个对象(迭代器),但不会立即开始执行。
  • 像__iter__()和_next__()这样的方法是自动实现的,因此,我们可以使用next()遍历这些项。
  • 一旦函数产生结果,函数就会暂停,控制就会转移给调用者。
  • 局部变量及其状态在连续调用之间被记住。
  • 最后,当函数终止时,在进一步调用时将自动引发StopIteration。

这里有一个例子来说明上面提到的所有要点,我们有一个名为my_gen()的生成器函数,其中包含几个yield语句。

# 一个简单的生成器函数
def my_gen():
    n = 1
    print('首先打印出来')
    # 生成器函数包含yield语句
    yield n

    n += 1
    print('这是第二次打印')
    yield n

    n += 1
    print('终于打印出来了')
    yield n

下面给出了解释器中的交互式运行,在Python shell中运行这些代码以查看输出。

>>> # 它返回一个对象,但不立即开始执行。
>>> a = my_gen()
>>> # 我们可以使用next()迭代这些项。
>>> next(a)
首先打印出来
1
>>> # 一旦函数产生结果,函数就会暂停,控制就会转移给调用者。
>>> # 局部变量及其状态在连续调用之间被记住。
>>> next(a)
这是第二次打印
2
>>> next(a)
终于打印出来了
3
>>> # 最后,当函数终止时,在进一步调用时将自动引发StopIteration。
>>> next(a)
Traceback (most recent call last):
...
StopIteration
>>> next(a)
Traceback (most recent call last):
...
StopIteration

在上面的例子中需要注意的一件有趣的事情是,变量n的值在每次调用之间都会被记住。

与普通函数不同,局部变量在函数产生时不会被破坏,此外,生成器对象只能迭代一次。

为了重新启动这个过程,我们需要使用类似于a = my_gen()的方法来创建另一个生成器对象。

注意:最后要注意的是,我们可以直接将生成器与for循环一起使用。

这是因为,for循环接受一个迭代器,并使用next()函数对其进行迭代,当StopIteration被触发时,它会自动结束。

# 一个简单的生成器函数
def my_gen():
    n = 1
    print('首先打印出来')
    # 生成器函数包含yield语句
    yield n

    n += 1
    print('这是第二次打印')
    yield n

    n += 1
    print('终于打印出来了')
    yield n

# 使用for循环
for item in my_gen():
    print(item)   

带有循环的Python生成器

上面的例子用处不大,我们研究它只是为了了解背景中发生了什么。

通常,生成器函数是通过具有适当终止条件的循环来实现的。

让我们以反转字符串的生成器为例。

def rev_str(my_str):
    length = len(my_str)
    for i in range(length - 1,-1,-1):
        yield my_str[i]

# For循环来反转字符串
# 输出:
# o
# l
# l
# e
# h
for char in rev_str("hello"):
     print(char)

在本例中,我们使用range()函数使用for循环以相反的顺序获取索引。

事实证明,这个生成器函数不仅可以处理字符串,还可以处理其他类型的迭代器,如列表、元组等。

Python生成器表达式

使用生成器表达式可以方便地动态创建简单的生成器,它使建造生成器变得容易。

与lambda函数创建匿名函数一样,生成器表达式创建匿名生成器函数。

生成器表达式的语法类似于Python中的列表理解,但是方括号被圆括号代替了。

列表理解和生成器表达式之间的主要区别是,列表理解生成整个列表,而生成器表达式一次生成一个项。

他们有点懒惰,只在被要求时才生产条目,由于这个原因,生成器表达式比等价的列表理解更有效。

# 初始化列表
my_list = [1, 3, 6, 10]

# 输出: [1, 9, 36, 100]
[x**2 for x in my_list]

# 同样的事情可以使用生成器表达式来完成
# 输出: <generator object <genexpr> at 0x0000000002EBDAF8>
(x**2 for x in my_list)

我们可以看到上面的生成器表达式并没有立即生成所需的结果。相反,它返回一个生成器对象,该对象根据需要生成项目。

# 初始化列表
my_list = [1, 3, 6, 10]

a = (x**2 for x in my_list)
# 输出: 1
print(next(a))

# 输出: 9
print(next(a))

# 输出: 36
print(next(a))

# 输出: 100
print(next(a))

# 输出: StopIteration
next(a)

生成器表达式可以在函数中使用,当以这种方式使用时,圆括号可以去掉。

>>> sum(x**2 for x in my_list)
146
>>> max(x**2 for x in my_list)
100

为什么在Python中使用生成器?

有几个原因使生成器成为一个有吸引力的实现。

1. 容易实现

与它们的迭代器类对应项相比,生成器可以以一种清晰而简洁的方式实现。下面是一个使用iterator类实现2的幂序列的例子。

class PowTwo:
    def __init__(self, max = 0):
        self.max = max
    def __iter__(self):
        self.n = 0
        return self
    def __next__(self):
        if self.n > self.max:
            raise StopIteration
        result = 2 ** self.n
        self.n += 1
        return result

这是漫长的。。。现在让我们使用生成器函数做同样的事情。

def PowTwoGen(max = 0):
    n = 0
    while n < max:
        yield 2 ** n
        n += 1

因为,生成器自动跟踪细节,所以在实现时非常简洁和清晰。

2. 节约内存

一个返回序列的普通函数将在返回结果之前在内存中创建整个序列,如果序列中的项数非常大,那么这是一种多余的方法。

这种序列的生成器实现是内存友好的,并且是首选的,因为它一次只生成一个项。

3.表示无限流

生成器是表示无限数据流的极好媒介,无限流不能存储在内存中,因为生成器一次只生成一个项,所以它可以表示无限数据流。

下面的例子可以生成所有的偶数(至少在理论上是这样)。

def all_even():
    n = 0
    while True:
        yield n
        n += 2

4. 管道生成器

生成器可用于输送一系列操作,这可以用一个例子来最好地说明。

假设我们有一个来自著名快餐连锁店的日志文件。日志文件中有一列(第4列)记录每小时售出的披萨数量,我们希望将其加起来计算出5年内售出的披萨总数。

假设所有内容都是字符串,不可用的数字被标记为’N/A’。它的生成器实现可以如下所示。

with open('sells.log') as file:
    pizza_col = (line[3] for line in file)
    per_hour = (int(x) for x in pizza_col if x != 'N/A')
    print("总卖出披萨 = ",sum(per_hour))

这个管道操作非常有效,并且易于阅读,这要方便得多!

赞(0)
未经允许不得转载:srcmini » python3生成器generator – Python3教程

评论 抢沙发

评论前必须登录!