Python 进阶_生成器 & 生成器表达式

简介: 目录目录相关知识点生成器生成器 fab 的执行过程生成器和迭代器的区别生成器的优势加强的生成器特性生成器表达式生成器表达式样例小结相关知识点Python 进阶_迭代器 & 列表解析 生成器带有 yield 关键字的的函数在 Python 中被称之为 generator(生成器)。

目录

相关知识点

Python 进阶_迭代器 & 列表解析

生成器

带有 yield 关键字的的函数在 Python 中被称之为 generator(生成器)。Python 解释器会将带有 yield 关键字的函数视为一个 generator 来处理。一个函数或者子程序都只能 return 一次,但是一个生成器能暂停执行并返回一个中间的结果 —— 这就是 yield 语句的功能 : 返回一个中间值给调用者并暂停执行。

EXAMPLE

In [94]: def fab(max):
    ...:     n, a, b = 0, 0, 1
    ...:     while n < max:
    ...:         yield b
    ...:         a, b = b, a + b
    ...:         n = n + 1
    ...:

In [95]: f = fab(5)

In [96]: f.next()
Out[96]: 1

In [97]: f.next()
Out[97]: 1

In [98]: f.next()
Out[98]: 2

In [99]: f.next()
Out[99]: 3

In [100]: f.next()
Out[100]: 5

In [101]: f.next()
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-101-c3e65e5362fb> in <module>()
----> 1 f.next()

StopIteration:

生成器 fab() 的执行过程

执行语句 f = fab(5) 时,并不会马上执行 fab() 函数的代码块,而是首先返回一个 iterable 对象!
在 for 循环语句执行时,才会执行 fab() 函数的代码块。
执行到语句 yield b 时,fab() 函数会返回一个迭代值,直到下次迭代前,程序流会回到 yield b 的下一条语句继续执行,然后再次回到 for 循环,如此迭代直到结束。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。
由此可以看出,生成器通过关键字 yield 不断的将迭代器返回到内存进行处理,而不会一次性的将对象全部放入内存,从而节省内存空间。从这点看来生成器和迭代器非常相似,但如果更深入的了解的话,其实两者仍存在区别。

生成器和迭代器的区别

生成器的另一个优点就是它不要求你事先准备好整个迭代过程中所有的元素,即无须将对象的所有元素都存入内存之后,才开始进行操作。生成器仅在迭代至某个元素时才会将该元素放入内存,而在这之前或之后,元素可以不存在或者被销毁。这个特点使得它特别适合用于遍历一些巨大的或是无限的类序列对象,EG. 大文件/大集合/大字典/斐波那契数列等。这个特点被称为 延迟计算惰性求值(Lazy evaluation),可以有效的节省内存。惰性求值实际上是现实了协同程序 的思想。

协同程序:是一个可以独立运行的函数调用,该调用可以被暂停或者挂起,之后还能够从程序流挂起的地方继续或重新开始。当协同程序被挂起时,Python 就能够从该协同程序中获取一个处于中间状态的属性的返回值(由 yield 返回),当调用 next() 方法使得程序流回到协同程序中时,能够为其传入额外的或者是被改变了的参数,并且从上次挂起的下一条语句继续执行。这是一种类似于进程中断的函数调用方式。这种挂起函数调用并在返回属性中间值后,仍然能够多次继续执行的协同程序被称之为生成器。

NOTE:而迭代器是不具有上述的特性的,不适合去处理一些巨大的类序列对象,所以建议优先考虑使用生成器来处理迭代的场景。

生成器的优势

综上所述:使用生成器最好的场景就是当你需要以迭代的方式去穿越一个巨大的数据集合。比如:一个巨大的文件/一个复杂的数据库查询等。

EXAMPLE 2:读取一个大文件

def read_file(fpath): 
    BLOCK_SIZE = 1024 
    with open(fpath, 'rb') as f: 
        while True: 
            block = f.read(BLOCK_SIZE) 
            if block: 
                yield block 
            else: 
                return

如果直接对文件对象调用 read() 方法,会导致不可预测的内存占用。好的方法是利用固定长度的缓冲区来不断读取文件的部分内容。通过 yield,我们不再需要编写读文件的迭代类,就可以轻松实现文件读取。

加强的生成器特性

除了可以使用 next() 方法来获取下一个生成的值,用户还可以使用 send() 方法将一个新的或者是被修改的值返回给生成器。除此之外,还可以使用 close() 方法来随时退出生成器。
EXAMPLE 3:

In [5]: def counter(start_at=0):
   ...:     count = start_at
   ...:     while True:
   ...:         val = (yield count)
   ...:         if val is not None:
   ...:             count = val
   ...:         else:
   ...:             count += 1
   ...:

In [6]: count = counter(5)

In [7]: type(count)
Out[7]: generator

In [8]: count.next()
Out[8]: 5

In [9]: count.next()
Out[9]: 6

In [10]: count.send(9)           # 返回一个新的值给生成器中的 yield count
Out[10]: 9

In [11]: count.next()
Out[11]: 10

In [12]: count.close()          # 关闭一个生成器

In [13]: count.next()
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-13-3963aa0a181a> in <module>()
----> 1 count.next()

StopIteration:

生成器表达式

生成器表达式是列表解析的扩展,就如上文所述:生成器是一个特定的函数,允许返回一个中间值,然后挂起代码的执行,稍后再恢复执行。列表解析的不足在于,它必须一次性生成所有的数据,用以创建列表对象,所以不适用于迭代大量的数据。
生成器表达式通过结合列表解析和生成器来解决这个问题。

  • 列表解析
    [expr for iter_var in iterable if cond_expr]

  • 生成器表达式
    (expr for iter_var in iterable if cond_expr)

两者的语法非常相似,但生成器表达式返回的不是一个列表类型对象,而是一个生成器对象,生成器是一个内存使用友好的结构。

生成器表达式样例

通过改进查找文件中最长的行的功能实现来看看生成器的优势。
EXAMPLE 4 : 一个比较通常的方法,通过循环将更长的行赋值给变量 longest

f = open('FILENAME', 'r')
longest = 0
while True:
    linelen = len(f.readline().strip())
    if not linelen:
        break
    if linelen > longest:
        longest = linelen
f.close()

return longest

很明显的,在这里例子中,需要迭代的对象是一个文件对象。

改进 1
需要注意的是,如果我们读取一个文件所有的行,那么我们应该尽早的去释放这个文件资源。例如:一个日志文件,会有很多不同的进程会其进行操作,所以我们不能容忍任意一个进程拿着这个文件的句柄不放。

f = open('FILENAME', 'r')
longest = 0
allLines = f.readlines()
f.close()
for line in allLines:
    linelen = len(line.strip())
    if not linelen:
        break
    if linelen > longest:
        longest = linelen

return longest

改进 2
我们可以使用列表解析来简化上述的代码,例如:在得到 allLines 所有行的列表时对每一行都进行处理。

f = open('FILENAME', 'r')
longest = 0
allLines = [x.strip() for x in f.readlines()]
f.close()
for line in allLines:
    linelen = len(line)
    if not linelen:
        break
    if linelen > longest:
        longest = linelen

return longest

改进 3
当我们处理一个巨大的文件时,file.readlines() 并不是一个明智的选择,因为 readlines() 会读取文件中所有的行。那么我们是否有别的方法来获取所有行的列表呢?我们可以应用 file 文件内置的迭代器。

f = open('FILENAME', 'r')
allLinesLen = [line(x.strip()) for x in f]
f.close()
return max(allLinesLen)   # 返回列表中最大的数值

不再需要使用循环比较并保留当前最大值的方法来处理,将所有行的长度最后元素存放在列表对象中,再获取做大的值即可。

改进 4
这里仍然存在一个问题,就是使用列表解析来处理 file 对象时,会将 file 所有的行都读取到内存中,然后再创建一个新的列表对象,这是一个内存不友好的实现方式。那么,我们就可以使用生成器表达式来替代列表解析。

f = open('FILENAME', 'r')
allLinesLen = (line(x.strip()) for x in f)   # 这里的 x 相当于 yield x
f.close()
return max(allLinesLen)

因为如果在函数中使用生成器表达式作为参数时,我们可以忽略括号 ‘()’,所以还能够进一步简化代码:

f = open('FILENAME', 'r')
longest = max(line(x.strip()) for x in f)
f.close()
return longest

最后:我们能够以一行代码实现这个功能,让 Python 解析器去处理打开的文件。
当然并不是说代码越少就越好,例如下面这一行代码每循环一次就会调用一个 open() 函数,效率上并没有 改进 4 更高。

return max(line(x.strip()) for x in open('FILENAME'))

小结

在需要迭代穿越一个对象时,我们应该优先考虑使用生成器替代迭代器,使用生成器表达式替代列表解析。当然这并不是绝对的。 迭代器和生成器是 Python 很重要的特性,对其有很好的理解能够写出更加 Pythonic 的代码。

相关文章
|
1月前
|
算法 大数据 Python
Python生成器:优雅而高效的迭代器
Python生成器:优雅而高效的迭代器
|
1月前
|
Python
Python如何使用生成器生成更加优雅和高效的代码
Python如何使用生成器生成更加优雅和高效的代码
23 0
|
2天前
|
开发者 索引 Python
Python中的海象运算符:简洁而强大的赋值表达式
【4月更文挑战第17天】Python 3.8 引入了海象运算符 `:=`,也称赋值表达式运算符,用于在表达式内部赋值,简化代码并提升可读性。它能用于条件判断、循环控制和函数参数等场景,优化逻辑流程。然而,使用时需注意可读性、运算符优先级及赋值限制,以确保代码清晰易懂。海象运算符是Python编程的一个有用工具,但应根据情况谨慎使用。
|
2天前
|
缓存 大数据 数据处理
Python迭代器、生成器和装饰器探究
【4月更文挑战第2天】 迭代器是遍历集合元素的对象,实现`__iter__()`和`__next__()`方法。示例中自定义迭代器`MyIterator`用于生成整数序列。 - 生成器简化了迭代器实现,利用`yield`关键词实现状态保存,减少内存占用。示例中的`my_generator`函数即为一个生成器。 - 装饰器用于修改函数行为,如日志记录、性能分析。装饰器`my_decorator`在函数调用前后添加额外代码。
4 0
|
3天前
|
Python
python面型对象编程进阶(继承、多态、私有化、异常捕获、类属性和类方法)(上)
python面型对象编程进阶(继承、多态、私有化、异常捕获、类属性和类方法)(上)
37 0
|
3天前
|
大数据 数据处理 开发者
深入理解Python中的迭代器和生成器
Python中的迭代器和生成器是实现高效循环和处理大型数据集的重要工具。本文将深入探讨迭代器和生成器的概念、原理以及在实际开发中的应用场景,帮助读者更好地理解和利用这些强大的工具。
|
7天前
|
存储 大数据 Python
「Python系列」Python迭代器与生成器
Python迭代器是一个可以记住遍历的位置的对象。迭代器对象必须实现两个方法,`__iter__()` 和 `__next__()`。字符串、列表或元组等数据类型都是可迭代对象,但它们不是迭代器,因为它们没有实现 `__next__()` 方法。
10 0
|
17天前
|
人工智能 机器人 测试技术
【Python】Python迭代器与生成器的区别(详细讲解)
【Python】Python迭代器与生成器的区别(详细讲解)
【Python】Python迭代器与生成器的区别(详细讲解)
|
25天前
|
程序员 数据处理 开发者
【Python小技巧】通过实例说明推导式,条件表达式和Lambda函数
【Python小技巧】通过实例说明推导式,条件表达式和Lambda函数
23 2
|
28天前
|
Python
探索Python集合推导式的进阶应用
探索Python集合推导式的进阶应用