转载至:https://www.bytelang.com/article/content/NQbmUaRIXyA=python
要想建立一个iterator,必须实现一个有__iter__()和__next__()方法的类,类要可以跟踪内部状态而且在没有元素返回的时候引起StopIteration异常.express
这个过程很繁琐并且违反直觉.Generator可以解决这个问题.函数
python generator是一个简单的建立iterator的途径.前面讲的那些繁琐的步骤均可以被generator自动完成.工具
简单来讲,generator是一个可以返回迭代器对象的函数.oop
就像建立一个函数同样简单,只不过不使用return 声明,而是使用yield声明.spa
若是一个函数至少包含一个yield声明(固然它也能够包含其余yield或return),那么它就是一个generator. 日志
yield和return都会让函数返回一些东西,区别在于,return声明完全结束一个函数,而yield声明是暂停函数,保存它的全部状态,而且后续被调用后会继续执行.code
下面这个例子说明上述所有要点,咱们有一个名为my_gen()的函数,它带有一些yield声明.对象
# A simple generator function def my_gen(): n = 1 print('This is printed first') # Generator function contains yield statements yield n n += 1 print('This is printed second') yield n n += 1 print('This is printed at last') yield n
在线实例:https://www.bytelang.com/o/s/c/nDeJ2dm7FUo=ip
有趣的是,在这个例子里变量n在每次调用之间都被记住了。和通常函数不一样的是,在函数yield以后本地变量没有被销毁,并且,generator对象只能被这样迭代一次。
要想重复上面的过程,须要相似 a = my_gen() 这样建立另外一个generator对象,并对其使用next方法迭代。
注意
:咱们能够对generator对象直接使用for循环。
这是由于一个for循环接收一个iterator对象,且使用next()函数迭代它,当遇到StopIteration异常的时候自动中止。
# A simple generator function def my_gen(): n = 1 print('This is printed first') # Generator function contains yield statements yield n n += 1 print('This is printed second') yield n n += 1 print('This is printed at last') yield n # Using for loop # Output: # This is printed first # 1 # This is printed second # 2 # This is printed at last # 3 for item in my_gen(): print(item)
在线示例:https://www.bytelang.com/o/s/c/3py5nUg_WVI=
上面的例子没有实际的应用意义,咱们只是为了探究背后原理。
一般来讲,generator都是和循环结合实现的,且这个循环带有一个终止条件。
咱们来看一个reverse一个字符串的例子
def rev_str(my_str): length = len(my_str) for i in range(length - 1,-1,-1): yield my_str[i] # For loop to reverse the string # Output: # o # l # l # e # h for char in rev_str("hello"): print(char)
在线示例:https://www.bytelang.com/o/s/c/_rs3yQEbIhE=
咱们在for循环里面使用range()函数来获取反向顺序的index。
generator除了能够应用于string,还能够应用于其它类型的iterator,例如list,tuple等。
使用generator表达式能够很容易地建立简单的generator。
就像lambda函数能够建立匿名函数同样,generator函数建立一个匿名generator函数。
generator表达式的语法相似于python的list comprehension,只是方括号被替换为了圆括号而已。
list comprehension和generator表达式的主要区别在于,前者产生所有的list,后者每次仅产生一项。
它们有些懒惰,仅在接到请求的时候才会产生输出。所以,generator表达式比list comprehension更加节省内存。
# Initialize the list my_list = [1, 3, 6, 10] # square each term using list comprehension # Output: [1, 9, 36, 100] [x**2 for x in my_list] # same thing can be done using generator expression # Output: <generator object <genexpr> at 0x0000000002EBDAF8> (x**2 for x in my_list)
在线示例:https://www.bytelang.com/o/s/c/BgIb7R1NCls=
上面的例子中,generator表达式没有当即产生须要的结果,而是在须要产生item的时候返回一个generator对象。
# Intialize the list my_list = [1, 3, 6, 10] a = (x**2 for x in my_list) # Output: 1 print(next(a)) # Output: 9 print(next(a)) # Output: 36 print(next(a)) # Output: 100 print(next(a)) # Output: StopIteration next(a)
在线示例:https://www.bytelang.com/o/s/c/p1^6fITXP5A=
generator表达式能够在函数内部使用。当这样使用的时候,圆括号能够丢弃。
相对于iterator类来讲,generator的实现清晰、简洁。下面是用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
generator这样实现
def PowTwoGen(max = 0): n = 0 while n < max: yield 2 ** n n += 1
由于generator自动跟踪实现细节,所以更加清晰、简洁。
一个函数返回一个序列(sequence)的时候,会在内存里面把这个序列构建好再返回。若是这个序列包含不少数据的话,就过犹不及了。
而若是序列是以generator方式实现的,就是内存友好的,由于他每次只产生一个item。
generator是一个很棒的表示无限数据流的工具。无限数据流不能被保存在内存里面,而且由于generator每次产生一个item,它就能够表示无限数据流。
下面的代码能够产生全部的奇数
def all_even(): n = 0 while True: yield n n += 2
generator能够对一系列操做执行流水线操做。
假设咱们有一个快餐连锁店的日志。日志的第四列是每小时售出的披萨数量,咱们想对近5年的这一数据进行求和。
假设全部数据都是字符,不可用的数据都以"N/A"表示,使用generator能够这样实现
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("Total pizzas sold = ",sum(per_hour))
这个流水线既高效又易读,而且看起来很酷!:)