5.python中迭代的奇淫巧技
- Python中的迭代方式
首先肯定是利用for循环进行遍历,其次我们还可以使用next函数来进行遍历
with open(‘/etc/passwd’) as f:
try: while True: line = next(f) print(line, end=”) except StopIteration: pass |
需要注意的是,使用next函数在遍历超过了最大限制的时候,会抛出StopIteration异常
如果不希望抛出异常的话,那么看见可以使用在next函数中传入一个值来指定当达到结尾的时候的返回
line = next(f, None)
- 实现自定义类的迭代
如果有一个自定义对象,希望支持迭代操作
那么可以定义一个 __iter__() 方法,内部进行相关的实现
比如我们类对象之中有着一个序列,那么我们定义一个__iter__()并且配合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) |
如果我们利用for或者next来遍历这个对象的时候,实际遍历的就是children
Iter还支持传入一个结尾值,这样会不断的调用callable对象直到返回值和标记值相等而已
比如如下代码
def reader2(s):
for chunk in iter(lambda: s.recv(CHUNKSIZE), b”):
pass
- yield关键字
在python中,支持使用yield关键字来声明一个生成器函数,比如下面
def frange(start, stop, increment):
x = start while x < stop: yield x x += increment |
这样我们就可以如下的使用
>>> for n in frange(0,4,5)
print(n)
其中我们yield后跟的就是返回的结果
之后我们看一个支持多层生成器规则的自定义类对象
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() |
这样在使用depth_first遍历的时候,首先会返回自己本身,然后再去迭代每一个子节点
不断的递归迭代到最后一层子元素
- 反向迭代
如果想要反向迭代一个序列的,可以考虑使用内置的reversed繁华
for x in reversed(a):
print(x)
对于这样的一个函数,需要对应对象实现 __reversed()__特殊方法来实现
- 利用生成器进行切片
对于一般的序列,我们可以直接使用切片,但是对于迭代器的话,我们不能直接使用切片,当然可以不断的遍历获取一个序列,然后切片,但是比较麻烦
不如考虑itertools.islice() 正好方便在叠搭起或者生成器上进行切片操作
>>> def count(n):
… while True: … yield n … n += 1 >>> # Now using islice() >>> import itertools >>> for x in itertools.islice(c, 10, 20): … print(x) |
其实内部就是不断的遍历然后丢弃不符合的元素,直到达到了指定开始为止,之后才会返回符合的元素
而且,支持类似切片的语法,可以在开始或者结束位置上传入None
这样和切片结果一致
如果希望跳过开头的部分元素的话,可以使用itertools.dropwhile() 其会抛弃开头的部分元素,那么抛弃的元素,是依赖于传入的一个函数判断,一致抛弃,直到函数会返回False
>>> from itertools import dropwhile
>>> with open(‘/etc/passwd’) as f:
… for line in dropwhile(lambda line: not line.startswith(‘#’), f):
… print(line, end=”)
这样会抛弃文件中,开头带#的行
在itertools中还有着很多好使的函数,比如给定一个序列,我们希望指导这个序列中所有可能的排列组合,可以使用其中的permutations函数
>>> items = [‘a’, ‘b’, ‘c’]
>>> from itertools import permutations
>>> for p in permutations(items):
… print(p)
其会获取到所有可能的排列
当然permutations函数支持传入一个长度,来获取到指定长度的所有排列
>>> for p in permutations(items, 2):
… print(p) … (‘a’, ‘b’) (‘a’, ‘c’) (‘b’, ‘a’) (‘b’, ‘c’) (‘c’, ‘a’) (‘c’, ‘b’) >>> |
还有一个类似的,combinations,其会列出所有元素的组合
类似先set,在排列
>>> from itertools import combinations
>>> for c in combinations(items, 3): … print(c) … (‘a’, ‘b’, ‘c’) >>> for c in combinations(items, 2): … print(c) … (‘a’, ‘b’) (‘a’, ‘c’) (‘b’, ‘c’) >>> for c in combinations(items, 1): … print(c) … (‘a’,) (‘b’,) (‘c’,) |
- 使用下标进行迭代
如果我们希望使用下标进行迭代的话,我们使用内置的enumerate函数
函数会返回两个值
>>> my_list = [‘a’, ‘b’, ‘c’]
>>> for idx, val in enumerate(my_list):
… print(idx, val)
会返回从0开始的下标供使用
如果希望按照传统行号开始,那么可以传递一个开始参数
>>> my_list = [‘a’, ‘b’, ‘c’]
>>> for idx, val in enumerate(my_list, 1):
… print(idx, val)
- 同时迭代多个序列
如果我们有多个序列,比如
>>> xpts = [1, 5, 4, 2, 10, 7]
>>> ypts = [101, 78, 37, 15, 62, 99]
我们希望以dict的方式进行迭代,那么我们可以使用zip函数
>>> for x, y in zip(xpts, ypts):
… print(x,y)
…
1 101
5 78
4 37
Zip函数,会返回一个同时迭代多个序列的迭代器,默认行为是如果某个序列到达了结尾就会结束迭代,所以默认迭代长度是序列中的最短序列
如果希望修改这个行为,则需要更换函数,使用zip_longest()函数
>>> from itertools import zip_longest
>>> for i in zip_longest(a,b):
… print(i)
…
(1, ‘w’)
(2, ‘x’)
(3, ‘y’)
(None, ‘z’)
Zip函数会创建一个迭代器进行返回,如果传入多于两个序列的参数,则可以通过元组进行承接
- 一行代码迭代不同的序列
itertools.chain()函数会接受多个序列对象,并返回一个迭代器,这个迭代器会屏蔽多个容器中的迭代细节
>>> from itertools import chain
>>> a = [1, 2, 3, 4]
>>> b = [‘x’, ‘y’, ‘z’]
>>> for x in chain(a, b):
… print(x)
这样我们打印的时候,就会在打印完成a之后打印b
而且这样的函数比起直接a+b,可以承接不同类型的列表
- 生成器来展开嵌套序列
如果希望将多个嵌套的序列flatmap为一个单层序列
那么可以使用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 |
上面我们利用isinstance来检测某个元素是否是可迭代的,
如果是,那么我们就使用yield from返回这个迭代器所有的值
这样我们就可以返回一个单层序列
>>> items = [‘Dave’, ‘Paula’, [‘Thomas’, ‘Lewis’]]
>>> for x in flatten(items):
… print(x)
…
Dave
Paula
Thomas
Lewis
>>>
yield from非常好使,如果不使用这个的话,就必须写额外的for循环了
那么我们有了yield from,我们就可以在下一节,讲述如何使用小量内存去创建数据处理管道
如果我们需要读取大量的文件,获取内部内容,我们可以使用生成器函数来做到
def gen_find(filepat, top):
”’ Find all filenames in a directory tree that match a shell wildcard pattern ”’ for path, dirlist, filelist in os.walk(top): for name in fnmatch.filter(filelist, filepat): yield os.path.join(path,name) def gen_opener(filenames): ”’ Open a sequence of filenames one at a time producing a file object. The file is closed immediately when proceeding to the next iteration. ”’ for filename in filenames: if filename.endswith(‘.gz’): f = gzip.open(filename, ‘rt’) elif filename.endswith(‘.bz2’): f = bz2.open(filename, ‘rt’) else: f = open(filename, ‘rt’) yield f f.close() def gen_concatenate(iterators): ”’ Chain a sequence of iterators together into a single sequence. ”’ for it in iterators: yield from it def gen_grep(pattern, lines): ”’ Look for a regex pattern in a sequence of lines ”’ pat = re.compile(pattern) for line in lines: if pat.search(line): yield line |
对应的使用可以如下
lognames = gen_find(‘access-log*’, ‘www’)
files = gen_opener(lognames)
lines = gen_concatenate(files)
pylines = gen_grep(‘(?i)python’, lines)
for line in pylines:
print(line)
这样我们使用yield语句作为数据的生产者,这样的好处是每一个生成器函数都很小而且是独立的,很容易就编写和维护
- 多个序列的merge
如果我们有多个序列,希望可以合并后得到一个排序序列并在上面迭代遍历
则可以使用heapq.merge() 函数
比如我们有两个序列
>>> import heapq
>>> a = [1, 4, 7, 10]
>>> b = [2, 5, 6, 11]
>>> for c in heapq.merge(a, b):
… print(c)
Merge函数要求多个序列必须要是有序的
则样才能进行合并使用