乐趣区

关于程序员:什么是-Python-中的生成器实现原理

本文首发自「慕课网」,想理解更多 IT 干货内容,程序员圈内热闻,欢送关注!

作者 | 慕课网精英讲师 朱广蔚

  1. 如何生成一个微小的序列
    1.1 需要形容
    要求生成一个蕴含很多元素的序列,假如:

存储 1 个整数须要 4 个字节
当初要创立一个蕴含 1 G 个整数的序列,从 0 到 1 1024 1024 * 1024 – 1
如果须要为序列中的每个整数分配内存,则须要调配的内存为 1G * 4 = 4G
1.2 通过列表推导
Python 提供了列表推导用于生成列表,上面应用列表推导生成一个蕴含 0 到 4 之间所有整数的列表,代码如下:

list = [i for i in range(4)]
list
[0, 1, 2, 3]
代码块 123
在第 1 行,应用列表推导创立一个蕴含 4 个元素的列表
在第 2 行,显示新创建的列表
在第 3 行,创立了一个蕴含 0、1、2、3 等 4 个元素的列表
如果生成一个从 0 到 1G 的列表,代码如下:

N = 1024 1024 1024
list = [i for i in range(N)]
Traceback (most recent call last):
File “<stdin>”, line 1, in <module>
File “<stdin>”, line 1, in <listcomp>
MemoryError
代码块 123456
在第 1 行,设定 N 为 1G
在第 2 行,应用列表推导创立一个蕴含 N 个元素的列表
在第 6 行,程序运行出错,提醒 MemoryError
应用列表推导创立蕴含 1G 个整数的列表时,须要为这 1G 个整数调配至多 4G 的内存,须要耗费大量的内存,超出了 Python 的限度,因而呈现了 MemoryError 的谬误。

另外,创立这个微小的列表须要耗费大量的工夫,因而执行第 2 行的语句后,零碎失去响应,大概 10 多秒后才呈现错误信息。

1.3 通过动静计算
列表推导须要一次性的为 1G 个整数分配内存空间,带来了两个问题:

列表占用了大量的物理内存
创立列表的工夫过长
Python 提供了一种动静计算的思路解决以上问题,它的思维如下:

要生成的序列是有规定的,在这个例子中,要求生成间断递增的序列
应用一个非凡的对象 generator,该对象被称为生成器 generator,生成器依照规定顺次输入该序列
Python 提供了内置办法 next(generator),该办法告诉生成器产生下一个数据并返回该数据
不须要为 generator 事后分配内存,通过调用 next(generator) 能够动静获取序列的下一个数据
创立一个输入从 0 到 1G 的生成器,代码如下:

N = 1024 1024 1024
generator = (i for i in range(N))
next(generator)
0
next(generator)
1
next(generator)
2
代码块 12345678
在第 1 行,设定 N 为 1G
在第 2 行,应用相似于列表推导的语法创立一个生成器,它输入从 0 到 1G 的序列留神:创立生成器的语法采纳小括号 (),创立列表的语法采纳方括号 []
在第 3 行,应用 next(generator),告诉 generator 生产一个数据在第 4 行,generator 输入从 0 到 1G 序列中的第 0 个整数
在第 5 行,应用 next(generator),告诉 generator 生产一个数据在第 6 行,generator 输入从 0 到 1G 序列中的第 1 个整数
在第 7 行,应用 next(generator),告诉 generator 生产一个数据在第 8 行,generator 输入从 0 到 1G 序列中的第 2 个整数
留神:在第 2 行,创立一个输入从 0 到 1G 的序列的生成器,因为不须要分配内存,创立生成器的速度十分快,简直是霎时实现的。与之相比,在上一节中创立一个输入从 0 到 1G 的序列的列表,因为须要分配内存,创立列表的速度十分慢,并且导致了 MemoryError。

  1. 生成器概述
    2.1 生成器的定义
    在 Python 中,生成器是一个非凡的对象,它依照肯定的规定顺次输入数据。Python 的内置函数 next(generator) 告诉生成器输入一个新的数据,当生成器输入全副数据后,产生一个非凡的异样 StopIteration,用于标记生成器输入完结。

上面的代码创立一个产生 0 到 3 之间所有整数的生成器:

generator = (i for i in range(3))
next(generator)
0
next(generator)
1
next(generator)
2
next(generator)
Traceback (most recent call last):
File “<stdin>”, line 1, in <module>
StopIteration
代码块 1234567891011
在第 1 行,创立一个产生 0 到 3 之间所有整数的生成器留神:创立生成器的语法采纳小括号 (),创立列表的语法采纳方括号 []
在第 2 行,生成器产生第 0 个整数
在第 4 行,生成器产生第 1 个整数
在第 6 行,生成器产生第 2 个整数
在第 8 行,生成器产生第 3 个整数在第 11 行,因为生成器生成的序列只蕴含 3 个整数,此时曾经生成全副的整数,因而抛出异样 StopIteration
2.2 应用 while 循环拜访生成器
依据生成器的原理,能够循环的调用 next(generator) 输入全副的序列,示例如下:

generator = (i for i in range(3))

while True:

try:
    item = next(generator)
    print(item)
except StopIteration:
    break

代码块 12345678
在第 1 行,创立一个产生 0 到 3 之间所有整数的生成器
在第 3 行,创立一个循环在第 5 行,调用 next(generator) 告诉生成器返回一个数据在第 7 行,当生成器输入完结后,抛出异样 StopIteration
运行程序,输入后果如下:

0
1
2
代码块 123
2.3 应用 for 循环拜访生成器
通常应用 for 循环拜访生成器,示例如下:

generator = (i for i in range(3))

for item in generator:

print(item)

代码块 1234
在第 1 行,创立一个产生 0 到 3 之间所有整数的生成器
在第 3 行,应用 for 循环拜访生成器
运行程序,输入后果如下:

0
1
2
代码块 123

  1. 创立生成器
    3.1 通过推导创立生成器
    能够应用相似于列表推导的语法创立一个生成器,语法如下:

(expression for i in iterable)
代码块 1
该生成器遍历对象 iterable,顺次产生数据 expression,它的工作流程如下:

for i in iterable:

generate expression

代码块 12
留神:创立生成器的语法与列表推导的语法类似,不同之处在于,创立生成器的语法采纳小括号 (),创立列表的语法采纳方括号 []。

通过推导创立生成器的示例如下:

generator = (i*2 for i in range(5))
for i in generator:

print(i)

代码块 123
循环变量 i 从 0 变动到 4
生成器每次产生数据 i*2
运行程序,输入后果如下:

0
2
4
6
8
代码块 12345
3.2 通过简单的推导创立生成器
能够应用相似于列表推导的语法创立一个生成器,语法如下:

(expression for i in iterable if condition)
代码块 1
该生成器遍历对象 iterable,如果条件 condition 为真,则产生数据 expression,它的工作流程如下:

for i in iterable:

if condition:
    generate expression

代码块 123
通过简单推导创立生成器的示例如下:

generator = (i for i in range(10) if i % 2 == 0)
for i in generator:

print(i)

代码块 123
循环变量 i 从 0 变动到 9
如果 i % 2 == 0,示意 i 是偶数
生成器每次产生从 0 到 9 之间的偶数
运行程序,输入后果如下:

0
2
4
6
8
代码块 12345
3.3 通过 yield 创立生成器
在生成器的生命周期中,生成器依据肯定的规定产生一系列的数据,生成器能够应用 yield 关键字产生一个数据。例如,一个生成特定范畴内的奇数序列的函数:

def generateOddNumbers(n):

for i in range(n):
    if i % 2 == 1:
        yield i

generator = generateOddNumbers(10)
for i in generator:

print(i)

代码块 12345678
在第 1 行,定义了函数 generateOddNumbers(n),它返回一个生成器,该生成器产生从 0 到 n 范畴内的奇数
在第 2 行到第 4 行,应用 for 循环生成从 0 到 n 范畴内的奇数在第 3 行,如果 i % 2 == 1 为真,示意 i 是奇数在第 4 行,应用 yield i 生成一个数据 i
在第 6 行,generateOddNumbers(10) 返回一个生成器,该生成器产生从 0 到 10 范畴内的奇数
在第 7 行,应用 for 循环遍历该生成器
运行该程序,输入如下:

1
3
5
7
9
代码块 12345
留神:蕴含 yield 关键字的函数被称为生成器函数,调用生成器函数会返回一个生成器。在下面的例子中,函数 generateOddNumbers(n) 蕴含 yield 关键字,是一个生成器函数,它返回一个生成器,该生成器产生从 0 到 n 范畴内的奇数。

  1. 应用 yield 实现遍历堆栈的生成器
    4.1 通过单链表实现堆栈
    通过单链表实现堆栈,图示如下:

![图片形容](
//img.mukewang.com/wiki/5ea92d460974644d07000133.jpg)

在上图中,每个节点有两个字段: item 和 next,item 用于存储数据,next 指向下一个节点,head 指针指向堆栈的顶部。形容堆栈的 Python 代码如下:

class Node:

def __init__(self, item):
    self.item = item
    self.next = None

class Stack:

def __init__(self):
    self.head = None

def push(self, item):
    node = Node(item)
    node.next = self.head
    self.head = node

stack = Stack()
stack.push(‘a’)
stack.push(‘b’)
stack.push(‘c’)
代码块 123456789101112131415161718
在第 1 行,定义了类 Node 用于形容链表中的节点
在第 6 行,定义了类 Stack 形容堆栈在第 8 行,定义了头指针 head,指向链表中的首个节点在第 10 行,定义了成员办法 push,将元素压如到堆栈中在第 11 行,创立一个新节点 node 在第 12 行,新节点 node 的 next 指向头结点在第 13 行,头结点指向新节点
在第 15 行,创立一个对象 stack
在第 16 行到第 18 行,顺次压入 3 个元素‘a’、‘b’、‘c’
4.2 应用 yield 关键字实现生成器函数
def stackGenerate(stack):

cursor = stack.head
while cursor != None:
    yield cursor.item
    cursor = cursor.next

代码块 12345
在第 1 行,定义函数 stackGenerate(stack)该函数蕴含 yield 关键字,是一个生成器函数,它返回一个生成器生成器遍历堆栈,按出栈的程序输入数据
在第 2 行,变量 cursor 指向了以后正在遍历的元素,初始化被设置为链表的头结点
在第 3 行,应用循环遍历堆栈如果变量 cursor 等于 None,示意曾经达到链表的尾部,即遍历齐全部的元素了在第 4 行,应用 yield 输入以后正在遍历的元素在第 5 行,将 cursor 指向下一个元素
4.3 通过 while 循环遍历堆栈
应用 while 循环显式的应用 next、StopIteration 实现对 stack 的遍历,代码如下:

generator = stackGenerate(stack)
while True:

try:
    item = next(generator)
    print(item)
except StopIteration:
    break

代码块 1234567
在第 1 行,stackGenerate(stack) 返回一个遍历堆栈的生成器
在第 4 行,next(generator) 获取生成器的输入
在第 6 行,当生成器输入完结后,抛出异样 StopIteration
程序顺次压入‘a’、‘b’、‘c’,遍历时以压入相同的程序输入,后果如下:

c
b
a
代码块 123
4.4 通过 for … in 循环遍历堆栈
通过 for … in 循环对生成器进行遍历,代码如下:

generator = stackGenerate(stack)
for item in generator:

print(item)

代码块 123
与上一节的代码相比,代码要简洁很多,程序输入雷同的后果如下:

c
b
a
代码块 123

欢送关注「慕课网」,发现更多 IT 圈优质内容,分享干货常识,帮忙你成为更好的程序员!

退出移动版