python cookbook阅读之——4.迭代器和生成器

4.1. 手动访问迭代器中的元素

要手动访问可迭代对象中的元素,可以使用next()函数,然后编写代码来捕获StopInteration异常

例如:采用手工方式从文件中读取问本行,StopInteration异常是用来通知迭代结束的,可以使用None命令它返回一个结束值

>>> with open('/home/zhaoyanyan/mytest') as f:
...     try:
...         while True:
...             line = next(f)
...             print (line, end='')
	    excepiton StopInteration:
		    pass

大多数时候使用for循环来访问可迭代对象中的元素,通过下面的例子解释迭代的详情

>>> items = [1,2,3]
>>> it = iter(items)
>>> next(it)
1
>>> next(it)
2
>>> next(it)
3
>>> next(it)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

4.2 反向迭代,可以使用内建的reversed()函数实现。

反向迭代只有在待处理的对象拥有可确定的大小,或者对象实现了__recersed__()特殊方法时才奏效。如果这两个条件都不满足,则必须先将这个对象转换为列表。(请注意:迭代对象转换为列表可能会消耗大量的内存,尤其是可迭代对象较大时)

>>> a = [1,2,3,4]
>>> for x in reversed(a):
...     print (x)
... 
4
3
2
1

4.3 迭代器的切片操作, intertools.islice()

>>> def count(n):
...     while True:
...             yield n
...             n += 1
... 
>>> c = count(0)
>>> c
<generator object count at 0x7f8f2204a730>
>>> c[10:20]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'generator' object has no attribute '__getitem__'

>>> #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
>>> 

迭代器和生成器是没法执行普通的切片操作的,这是因为不知道他们的长度是多少(而且他们也没有实现索引)。islice()产生的结果是一个迭代器,他可以产生出所需要的切片元素,但这是通过访问并丢弃所有起始索引之前的元素来实现的。之后的元素会由islice对象产生出来,知道到达结束索引为止。

重点强调:islice()会消耗所提供的迭代器中的数据。由于迭代器中的元素只能访问一次,没法倒回去,因此要注意。如果使用之后还需要倒回去访问前面的数据,那就应该先将数据转到列表中去。

4.4 跳过可迭代对象中的前一部分元素

问题:想对某个可迭代对象做迭代处理,但是对前面几个元素并不感兴趣,只想将他们丢弃掉。(例如:跳过注释)

使用itertools模块中的函数itertools.dropwhile()

>>> with open("/home/testdir/mytest") as f:
...     for line in dropwhile(lambda line: line.startswith("#"), f):
...             print line
... 
a1

b2



>>> 

如果要知道跳过多少个元素,可以使用itertools.islice(), 下面例子中的None表示想要前3个元素之外的所有元素,而不是只要前3个元素。(即表示切片[3:], 而不是[:3])

>>> from itertools import islice
>>> items = ['a', 'b', 'c', 1, 2, 3, 4]
>>> for x in islice(items, 3, None):
...     print x
... 
1
2
3
4
>>> 

4.5 迭代所有可能的组合或者排列,itertools模块提供了3个函数

(1)itertools.permutations()  接受一个元素集合,把所有排列组合可能的情况都列出来

>>> items = ["a", "b", "c"]
>>> from itertools import permutations
>>> for p in permutations(items):
...     print (p)
... 
('a', 'b', 'c')
('a', 'c', 'b')
('b', 'a', 'c')
('b', 'c', 'a')
('c', 'a', 'b')
('c', 'b', 'a')
>>> 

如果想得到较短长度的所有全排列,可以提供一个可选的长度参数。

>>> for p in permutations(items, 2):
...     print (p)
... 
('a', 'b')
('a', 'c')
('b', 'a')
('b', 'c')
('c', 'a')
('c', 'b')
>>> 

(2)itertools.combinations()可产生全部元素的组合元素,不予考虑实际顺序,例如组合('a','b')和('b','a')被认为是相等的。

>>> 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',)
>>> 

(3)itertools.combinations_with_replacement() 函数解放了元素重复限制,允许相同的元素多次选择

>>> from itertools import combinations_with_replacement
>>> for c in combinations_with_replacement(items, 3):
...     print (c)
... 
('a', 'a', 'a')
('a', 'a', 'b')
('a', 'a', 'c')
('a', 'b', 'b')
('a', 'b', 'c')
('a', 'c', 'c')
('b', 'b', 'b')
('b', 'b', 'c')
('b', 'c', 'c')
('c', 'c', 'c')

4.6 迭代一个序列,记录下序列中当前处理掉的元素索引。enumerate默认从0开始,从别的数字开始,可以自己传一个start参数作为索引。

>>> alist = [1,2,3]
>>> for index, value in enumerate(alist):
...     print(index, value)
... 
0 1
1 2
2 3
>>> for index, value in enumerate(alist, 1):
...     print(index, value)
... 
1 1
2 2
3 3
>>> 

4.6 将不同列表的数据配对在一起时,同时迭代多个序列:zip()。zip(a,b)的工作原理是创建出一个迭代器,该迭代器可产生元祖(x,y),x取自a序列,y取自b序列,当其中某个输入序列没有元素可以继续迭代时,整个迭代过程结束。因此,整个迭代的长度和其中最短的输入序列长度相同。若要输出到最长的序列为止,可以使用itertools.zip_longest()。fillvalue可以设置多出的列里对应的默认值。将配对的数据保存列表请使用list(zip())

>>> a = [1,2,3,4,5]
>>> b = ["a","b","c","d"]
>>> for ia, ib in zip(a, b):
...     print(ia, ib)
... 
1 a
2 b
3 c
4 d
>>> for i in zip(a,b):
...     print(i)
... 
(1, 'a')
(2, 'b')
(3, 'c')
(4, 'd')
>>> import itertools
>>> for i in itertools.zip_longest(a,b):
...     print(i)
... 
(1, 'a')
(2, 'b')
(3, 'c')
(4, 'd')
(5, None)
>>> for i in itertools.zip_longest(a,b, fillvalue=0):
...     print(i)
... 
(1, 'a')
(2, 'b')
(3, 'c')
(4, 'd')
(5, 0)
>>> zip(a,b)
<zip object at 0x7f6fd53060c8>
>>> list(zip(a,b))
[(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd')]
>>> 

4.7 itertools.chain()对许多对象执行相同的操作,但是这些对象包含在不同的容器中,避免写出嵌套循环处理,保持代码可读性。

>>> a = [1,2,3,4,5]
>>> b = ["a","b","c","d"]
>>> import itertools
>>> for x in itertools.chain(a,b):
...     print(x)
... 
1
2
3
4
5
a
b
c
d
>>> 

4.8 创建处理数据的管道

问题:
我们想以流水线式的的形式对数据迭代处理(类似unix下的管道)。比方说我们有海量的数据需要处理,但是没有完全将数据加载到内存中去。
解决方法:
生成器函数是一种实现管道机制的好方法。假设:我们有一个超大的目录,其中都是想要处理的日志文件:

foo/
	accsess-log-012007.gz
	accsess-log-022007.gz
	accsess-log-032007.gz
	...
	accsess-log-012008.gz
bar/
	access-log-092007.bz2
	...
	accsess-log-022008

假设每个文件都包含如下数据行:

124.115.6.12 - - [10/Jui/2012:00:18:50 -0500] "GET /robot.txt ..." 200 71
124.115.6.13 - - [10/Jui/2012:00:18:51 -0500] "GET /ply/ ..." 200 11875
124.115.6.14 - - [10/Jui/2012:00:18:51 -0500] "GET /blog/atom.xml ..." 304 -

要处理这些文件,可以定义一系列小型的生成器函数,每个函数执行特定的独立任务。示例:

import os
import fnmatch
import gzip
import bz2
import re

def gen_find(filepat, top):
	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):
	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):
	for it in iterators:
		yield form it

def gen_grep(pattern, lines):
	pat = re.compile(pattern)
	for line in lines:
		if pat.search(line):
			yield line

现在可以简单的将这些函数堆叠起来,行形成一个数据处理的管道。

例如:要找出所有包含关键词python的日志行,只需要这么做:

logname = gen_find("access-log*", "www")
files = gen_opener(logname)
lines = gen_concatence(files)
pylines = gen_grep('(?i)python', lines)
for line in pylines:
	print(line)

 如果稍后想对管道进行扩展,甚至可以在声称其表达式中填充数据。比如:下面这个版本可以找出传送的字节数并统计出总字节量:

lognames = gen_find("access-log*", "www")
files = gen_opener(lognames)
lines = gen_concatence(files)
pylines = gen_grep('(?i)python', lines)
bytecolumn = (line.rsplit(None,1)[1] for line in pylines)
bytes = (int(x) for x in bytecolumn if x != '-')
print('Total', sum(bytes))

4.9 扁平化处理嵌套型的序列

问题: 有一个嵌套型的序列,想将它扁平化处理为一列单独的值。

解决方案:写一个带有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
... 
>>>
>>> for x in flatten(items):
...      print(x)
... 
1
2
3
4
5
6
7
8
>>> 

4.10 合并有序序列

一组有序序列,想对它们合并在一起之后的有序序列进行迭代。
 

>>> import heapq
>>> a = [1,3,5,7]
>>> b = [2,4,6,8]
>>> heapq.merge(a,b)
<generator object merge at 0x7f85ee6d24f8>
>>> for i in heapq.merge(a,b):
...     print (i)
... 
1
2
3
4
5
6
7
8
>>> c = [2,4,5,7]
>>> for i in heapq.merge(a,c):
...     print (i)
... 
1
2
3
4
5
5
7
7
>>> 

heapq.merge的迭代性质意味着它对所有提供的序列都不会做一次性读取,可以用它来处理非常长的序列,而开销却非常小。
需注意:heapq.merge()要求所有 的输入序列都是有序的。特别是,它不会首先将所有的数据读取到堆栈中,或者预先做任何排序操作。它也不会对输入做任何验证,以检查它们是否满足有序的要求。相反它只是简单地检查每个输入序列中的第一个元素,将最小的那个发送出去。然后再从之前选择的序列中读取一个新的元素,再重复执行这个步骤,直到所有的输入序列都耗尽为止。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值