5.python中迭代的奇淫巧技

  1. 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)

  1. 实现自定义类的迭代

如果有一个自定义对象,希望支持迭代操作

那么可以定义一个 __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

  1. 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遍历的时候,首先会返回自己本身,然后再去迭代每一个子节点

不断的递归迭代到最后一层子元素

  1. 反向迭代

如果想要反向迭代一个序列的,可以考虑使用内置的reversed繁华

for x in reversed(a):

print(x)

对于这样的一个函数,需要对应对象实现 __reversed()__特殊方法来实现

  1. 利用生成器进行切片

对于一般的序列,我们可以直接使用切片,但是对于迭代器的话,我们不能直接使用切片,当然可以不断的遍历获取一个序列,然后切片,但是比较麻烦

不如考虑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’,)

  1. 使用下标进行迭代

如果我们希望使用下标进行迭代的话,我们使用内置的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)

  1. 同时迭代多个序列

如果我们有多个序列,比如

>>> 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函数会创建一个迭代器进行返回,如果传入多于两个序列的参数,则可以通过元组进行承接

  1. 一行代码迭代不同的序列

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,可以承接不同类型的列表

  1. 生成器来展开嵌套序列

如果希望将多个嵌套的序列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语句作为数据的生产者,这样的好处是每一个生成器函数都很小而且是独立的,很容易就编写和维护

  1. 多个序列的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函数要求多个序列必须要是有序的

则样才能进行合并使用

发表评论

邮箱地址不会被公开。 必填项已用*标注