4: 从另一个可迭代器中获取所有值
Python 3.x Version ≥ 3.3 如果要从另一个可迭代器中产生所有值,请使用 yield from :
def foob(x):
yield from range(x * 2)
yield from range(2)
list(foob(5)) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1]
这种方法也适用于生成器。
def fibto(n):
a, b = 1, 1
while True:
if a >= n: break
yield a
a, b = b, a + b
def usefib():
yield from fibto(10)
yield from fibto(20)
list(usefib()) # [1, 1, 2, 3, 5, 8, 1, 1, 2, 3, 5, 8, 13]
5: 迭代
生成器对象支持迭代器协议。也就是说,它提供了一个 next() 方法 (在 Python 3.x 中为 __next__()),用来逐步执行它,而它的 __iter__ 方法返回它自己。这意味着生成器可以用于任何支持泛型可迭代对象的语言构造中。
# Python 2.x xrange() 的部分实现
def xrange(n):
i = 0
while i < n:
yield i
i += 1
# looping
for i in xrange(10):
print(i) # prints the values 0, 1, ..., 9
# unpacking
a, b, c = xrange(3) # 0, 1, 2
# building a list
l = list(xrange(10)) # [0, 1, ..., 9]
6: next() 函数
内置的 next() 是一个方便的封装器,可用于从任何迭代器(包括生成器迭代器)接收值,并在迭代器耗尽时提供默认值。
def nums():
yield 1
yield 2
yield 3
generator = nums()
next(generator, None) # 1
next(generator, None) # 2
next(generator, None) # 3
next(generator, None) # None
next(generator, None) # None
# ...
语法是 next(iterator[, default])。如果迭代器结束并传递了默认值,则返回该值。如果没有提供默认值,则会引发 StopIteration。
7: 例程
生成器可用于实现例行程序:
# create and advance generator to the first yield
def coroutine(func):
def start(*args,**kwargs):
cr = func(*args,**kwargs)
next(cr)
return cr
return start
# example coroutine
@coroutine
def adder(sum = 0):
while True:
x = yield sum
sum += x
# example use
s = adder()
s.send(1) # 1
s.send(2) # 3
例程通常用于实现状态机,因为它们主要用于创建需要状态才能正常运行的单方法程序。它们对现有状态进行操作,并返回操作完成后获得的值。
8: Refactoring list-building code
重构列表构建代码
def create():
result = []
# logic here...
result.append(value) # possibly in several places
# more logic...
return result # possibly in several places
values = create()
当用列表理解替换内部逻辑不现实时,可以将整个函数就地转化为生成器,然后收集结果:
def create_gen():
# logic...
yield value
# more logic
return # not needed if at the end of the function, of course
values = list(create_gen())
如果逻辑是递归的,则使用 yield from 在 "扁平化" 结果中包含递归调用的所有值:
def preorder_traversal(node):
yield node.value
for child in node.children:
yield from preorder_traversal(child)
9: 递归:递归列出目录中的所有文件
首先,导入处理文件的库:
from os import listdir
from os.path import isfile, join, exists
只读取目录中文件的辅助函数:
def get_files(path):
for file in listdir(path):
full_path = join(path, file)
if isfile(full_path):
if exists(full_path):
yield full_path
另一个只获取子目录的辅助函数:
def get_directories(path):
for directory in listdir(path):
full_path = join(path, directory)
if not isfile(full_path):
if exists(full_path):
yield full_path
现在,使用这些函数递归获取一个目录及其所有子目录中的所有文件(使用生成器):
def get_files_recursive(directory):
for file in get_files(directory):
yield file
for subdirectory in get_directories(directory):
for file in get_files_recursive(subdirectory): # here the recursive call
yield file
这个函数可以通过以下方法简化:
def get_files_recursive(directory):
yield from get_files(directory)
for subdirectory in get_directories(directory):
yield from get_files_recursive(subdirectory)
10: 生成器表达式
可以使用类似于理解的语法创建生成器迭代器。
generator = (i * 2 for i in range(3))
next(generator) # 0
next(generator) # 2
next(generator) # 4
next(generator) # raises StopIteration
如果函数不一定需要传递一个列表,那么可以通过在函数调用中加入一个生成器表达式来节省字符(并提高可读性)。函数调用中的括号会隐含地使您的表达式成为一个生成器表达式。
sum(i ** 2 for i in range(4)) # 0^2 + 1^2 + 2^2 + 3^2 = 0 + 1 + 4 + 9 = 14
此外,您还可以节省内存,因为生成器允许 Python 根据需要使用值,而不是加载您要遍历的整个列表(上例中的[0, 1, 2, 3])。
11: 使用生成器查找斐波那契数
生成器的一个实际用例是遍历无穷级数的值。下面是一个查找斐波那契数列前十项的例子。
def fib(a=0, b=1):
"""Generator that yields Fibonacci numbers. `a` and `b` are the seed values"""
while True:
yield a
a, b = b, a + b
f = fib()
print(', '.join(str(next(f)) for _ in range(10)))
# Outputs: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
12: 搜索
即使不进行迭代,next 函数也很有用。向 next 传递一个生成器表达式,可以快速搜索与某个谓词匹配的元素的首次出现。程序代码如
def find_and_transform(sequence, predicate, func):
for element in sequence:
if predicate(element):
return func(element)
raise ValueError
item = find_and_transform(my_sequence, my_predicate, my_func)
可以替换为
item = next(my_func(x) for x in my_sequence if my_predicate(x))
# 如果没有匹配结果,将引发 StopIteration;如果需要,可以捕获并转换此异常。
为此,最好创建一个别名(如 first = next)或一个封装函数来转换异常:
def first(generator):
try:
return next(generator)
except StopIteration:
raise ValueError
13: 并行迭代生成器
要并行遍历多个生成器,请使用 zip 内置函数:
for x, y in zip(a,b):
print(x,y)
结果是
1 x
2 y
3 z
在 python 2 中,应使用 itertools.izip 代替。在这里,我们还可以看到所有 zip 函数都会产生元组。
请注意,一旦其中一个迭代项耗尽,zip 就会停止迭代。如果你想迭代最长的迭代项,请使用 itertools.zip_longest()。