python cookbook第四章笔记
4.1 手动遍历迭代器
你想遍历一个可迭代对象中的所有元素,但是却不想使用for循环。
为了手动的遍历可迭代对象,使用 next() 函数并在代码中捕获 StopIteration 异常。 比如,下面的例子手动读取一个文件中的所有行:
def manual_iter():
with open('/etc/passwd') as f:
try:
while True:
line = next(f)
print(line, end='')
except StopIteration:
pass
4.2 代理迭代
你构建了一个自定义容器对象,里面包含有列表、元组或其他可迭代对象。 你想直接在你的这个新容器对象上执行迭代操作。
实际上你只需要定义一个 iter() 方法,将迭代操作代理到容器内部的对象上去。比如:
class Node:
def __init__(self, value):
self._value = value
self._children = []
def __repr__(self):
return 'Node({!r})'.format(self._value)
def add_child(self, node):
self._children.append(node)
def __iter__(self):
return iter(self._children)
# Example
if __name__ == '__main__':
root = Node(0)
child1 = Node(1)
child2 = Node(2)
root.add_child(child1)
root.add_child(child2)
# Outputs Node(1), Node(2)
for ch in root:
print(ch)
在上面代码中, iter() 方法只是简单的将迭代请求传递给内部的 _children 属性。
4.3 使用生成器创建新的迭代模式
你想实现一个自定义迭代模式,跟普通的内置函数比如 range() , reversed() 不一样。
如果你想实现一种新的迭代模式,使用一个生成器函数来定义它。 下面是一个生产某个范围内浮点数的生成器:
def frange(start, stop, increment):
x = start
while x < stop:
yield x
x += increment
一个函数中需要有一个 yield 语句即可将其转换为一个生成器。 跟普通函数不同的是,生成器只能用于迭代操作。 一个生成器函数主要特征是它只会回应在迭代中使用到的 next 操作。 一旦生成器函数返回退出,迭代终止。
4.4 实现迭代器协议
你想构建一个能支持迭代操作的自定义对象,并希望找到一个能实现迭代协议的简单方法。
目前为止,在一个对象上实现迭代最简单的方式是使用一个生成器函数。 在4.2小节中,使用Node类来表示树形数据结构。你可能想实现一个以深度优先方式遍历树形节点的生成器。 下面是代码示例:
class Node:
def __init__(self, value):
self._value = value
self._children = []
def __repr__(self):
return 'Node({!r})'.format(self._value)
def add_child(self, node):
self._children.append(node)
def __iter__(self):
return iter(self._children)
def depth_first(self):
yield self
for c in self:
yield from c.depth_first()
# Example
if __name__ == '__main__':
root = Node(0)
child1 = Node(1)
child2 = Node(2)
root.add_child(child1)
root.add_child(child2)
child1.add_child(Node(3))
child1.add_child(Node(4))
child2.add_child(Node(5))
for ch in root.depth_first():
print(ch)
# Outputs Node(0), Node(1), Node(3), Node(4), Node(2), Node(5)
在这段代码中,depth_first() 方法简单直观。 它首先返回自己本身并迭代每一个子节点并 通过调用子节点的 depth_first() 方法(使用 yield from 语句)返回对应元素。
4.5 反向迭代
你想反方向迭代一个序列
使用内置的 reversed() 函数,比如:
>>> a = [1, 2, 3, 4]
>>> for x in reversed(a):
... print(x)
...
4
3
2
1
反向迭代仅仅当对象的大小可预先确定或者对象实现了 __reversed__() 的特殊方法时才能生效。 如果两者都不符合,那你必须先将对象转换为一个列表才行
4.6 带有外部状态的生成器函数
你想定义一个生成器函数,但是它会调用某个你想暴露给用户使用的外部状态值。
如果你想让你的生成器暴露外部状态给用户, 别忘了你可以简单的将它实现为一个类,然后把生成器函数放到 iter() 方法中过去
为了使用这个类,你可以将它当做是一个普通的生成器函数。 然而,由于可以创建一个实例对象,于是你可以访问内部属性值
4.7 迭代器切片
你想得到一个由迭代器生成的切片对象,但是标准切片操作并不能做到。
函数 itertools.islice() 正好适用于在迭代器和生成器上做切片操作。比如:
>>> def count(n):
... while True:
... yield n
... n += 1
...
>>> c = count(0)
>>> c[10:20]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'generator' object is not subscriptable
>>> # Now using islice()
>>> import itertools
>>> for x in itertools.islice(c, 10, 20):
... print(x)
...
10
11
12
13
14
15
16
17
18
19
>>>
4.8 跳过可迭代对象的开始部分
你想遍历一个可迭代对象,但是它开始的某些元素你并不感兴趣,想跳过它们。
itertools 模块中有一些函数可以完成这个任务。 首先介绍的是 itertools.dropwhile() 函数。使用时,你给它传递一个函数对象和一个可迭代对象。 它会返回一个迭代器对象,丢弃原有序列中直到函数返回Flase之前的所有元素,然后返回后面所有元素。
如果你已经明确知道了要跳过的元素的个数的话,那么可以使用 itertools.islice() 来代替。
4.9 排列组合的迭代
你想迭代遍历一个集合中元素的所有可能的排列或组合
itertools模块提供了三个函数来解决这类问题。 其中一个是 itertools.permutations() , 它接受一个集合并产生一个元组序列,每个元组由集合中所有元素的一个可能排列组成。 也就是说通过打乱集合中元素排列顺序生成一个元组
使用 itertools.combinations() 可得到输入集合中元素的所有的组合
在计算组合的时候,一旦元素被选取就会从候选中剔除掉(比如如果元素’a’已经被选取了,那么接下来就不会再考虑它了)。 而函数 itertools.combinations_with_replacement() 允许同一个元素被选择多次
4.10 序列上索引值迭代
你想在迭代一个序列的同时跟踪正在被处理的元素索引。
内置的 enumerate() 函数可以很好的解决这个问题:
>>> my_list = ['a', 'b', 'c']
>>> for idx, val in enumerate(my_list):
... print(idx, val)
...
0 a
1 b
2 c
4.11 同时迭代多个序列
你想同时迭代多个序列,每次分别从一个序列中取一个元素。
为了同时迭代多个序列,使用 zip() 函数。比如:
>>> xpts = [1, 5, 4, 2, 10, 7]
>>> ypts = [101, 78, 37, 15, 62, 99]
>>> for x, y in zip(xpts, ypts):
... print(x,y)
...
1 101
5 78
4 37
2 15
10 62
7 99
>>>
4.12 不同集合上元素的迭代
你想在多个对象执行相同的操作,但是这些对象在不同的容器中,你希望代码在不失可读性的情况下避免写重复的循环。
itertools.chain() 方法可以用来简化这个任务。 它接受一个可迭代对象列表作为输入,并返回一个迭代器,有效的屏蔽掉在多个容器中迭代细节。
4.13 创建数据处理管道
你想以数据管道(类似Unix管道)的方式迭代处理数据。 比如,你有个大量的数据需要处理,但是不能将它们一次性放入内存中。
4.14 展开嵌套的序列
你想将一个多层嵌套的序列展开成一个单层列表
可以写一个包含 yield from 语句的递归生成器来轻松解决这个问题。比如:
from collections import Iterable
def flatten(items, ignore_types=(str, bytes)):
for x in items:
if isinstance(x, Iterable) and not isinstance(x, ignore_types):
yield from flatten(x)
else:
yield x
items = [1, 2, [3, 4, [5, 6], 7], 8]
# Produces 1 2 3 4 5 6 7 8
for x in flatten(items):
print(x)
语句 yield from 在你想在生成器中调用其他生成器作为子例程的时候非常有用。 如果你不使用它的话,那么就必须写额外的 for 循环了。比如:
def flatten(items, ignore_types=(str, bytes)):
for x in items:
if isinstance(x, Iterable) and not isinstance(x, ignore_types):
for i in flatten(x):
yield i
else:
yield x
4.15 顺序迭代合并后的排序迭代对象
你有一系列排序序列,想将它们合并后得到一个排序序列并在上面迭代遍历。
heapq.merge() 函数可以帮你解决这个问题。比如:
>>> import heapq
>>> a = [1, 4, 7, 10]
>>> b = [2, 5, 6, 11]
>>> for c in heapq.merge(a, b):
... print(c)
...
1
2
4
5
6
7
10
11
有一点要强调的是 heapq.merge() 需要所有输入序列必须是排过序的。 特别的,它并不会预先读取所有数据到堆栈中或者预先排序,也不会对输入做任何的排序检测。 它仅仅是检查所有序列的开始部分并返回最小的那个,这个过程一直会持续直到所有输入序列中的元素都被遍历完。
4.16 迭代器代替while无限循环
你在代码中使用 while 循环来迭代处理数据,因为它需要调用某个函数或者和一般迭代模式不同的测试条件。 能不能用迭代器来重写这个循环呢?
iter 函数一个鲜为人知的特性是它接受一个可选的 callable 对象和一个标记(结尾)值作为输入参数。 当以这种方式使用的时候,它会创建一个迭代器, 这个迭代器会不断调用 callable 对象直到返回值和标记值相等为止。
这种特殊的方法对于一些特定的会被重复调用的函数很有效果,比如涉及到I/O调用的函数。 举例来讲,如果你想从套接字或文件中以数据块的方式读取数据,通常你得要不断重复的执行 read() 或 recv() , 并在后面紧跟一个文件结尾测试来决定是否终止。这节中的方案使用一个简单的 iter() 调用就可以将两者结合起来了。 其中 lambda 函数参数是为了创建一个无参的 callable 对象,并为 recv 或 read() 方法提供了 size 参数。