天天看点

第四章:迭代器与生成器第一节:关于迭代器

文章目录

  • 第一节:关于迭代器
      • ==手动遍历迭代器==
          • 使用 next() 进行迭代
      • ==代理迭代==
          • 使用__iter__ ()代理
      • ==使用生成器创建新的迭代模式==
      • ==反向迭代==
          • 使用内置的 reversed() 进行返向迭代
          • 在自定义类上实现 reversed ()
      • ==迭代器切片==
          • 使用 itertools.islice() 进行切片
      • ==跳过可迭代对象的开始部分==
          • 使用 itertools.dropwhile() 跳过
          • 使用 itertools.islice() 跳过
      • ==排列组合的迭代==
          • 使用 itertools.permutations() 迭代遍历
          • 使用 itertools.combinations() 迭代遍历
          • 使用 tertools.combinations with replacement() 迭代遍历
      • ==序列上索引值迭代==
          • 使用 enumerate() 进行索引值迭代
      • ==同时迭代多个序列==
          • 使用 zip() 进行迭代
          • 使用 itertools.zip longest() 进行迭代
      • ==按顺序迭代多个序列==
          • 使用 itertools.chain() 迭代
      • ==创建数据处理管道==
      • ==展开嵌套的序列==
            • 使用包含 yield from 语句的递归生成器展开
            • 使用 for 循环展开

第一节:关于迭代器

手动遍历迭代器

使用 next() 进行迭代

为了手动的遍历可迭代对象,使用 next() 函数并在代码中捕获 StopIteration 异

常。比如,下面的例子手动读取一个文件中的所有行:

例:

def manual_iter():
    with open('/etc/passwd') as f:
        try:
            while True:
                 line = next(f)
                 print(line, end='')
        except StopIteration:
             pass
           

代理迭代

使用__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)

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)     #Node(1)
                      #Node(2)
           

使用生成器创建新的迭代模式

例:生产某个范围内浮点数的生成器

#定义一个方法,返回一个可迭代对象
def frange(start, stop, increment):
	x = start
	while x < stop:
		yield x
		x += increment
		
#使用for遍历结果	
for n in frange(0, 4, 0.5):
print(n)

#或者使用list(),直接例表化
print(list(frange(0, 4, 0.5)))
           

一个函数中需要有一个 yield 语句即可将其转换为一个生成器。跟普通函数不同

的是,生成器只能用于迭代操作

一个生成器函数主要特征是它只会回应在迭代中使用到的 next 操作。

反向迭代

使用内置的 reversed() 进行返向迭代

例:

a = [1, 2, 3, 4]
for x in reversed(a):
    print(x)
'''
4
3
2
1
'''
           
【注意】当对象大小在可预见的情况,用此法,否则一定要转为列表才能使用

例:

f = open('somefile')
for line in reversed(list(f)):
	print(line, end='')
           
在自定义类上实现 reversed ()

例:

class Countdown():
    def __init__(self, start):
        self.start = start

    # Forward iterator
    def __iter__(self):
        n = self.start
        while n > 0:
            yield n
            n -= 1

    def __reversed__(self):
        n = 1
        while n <= self.start:
            yield n
            n += 1
#从1遍历到30
for rr in reversed(Countdown(30)):
    print(rr)
    
#从30遍历到1
for rr in Countdown(30):
    print(rr)
           

迭代器切片

使用 itertools.islice() 进行切片

例:

def count(n):
    while True:
        yield n
        n += 1
c = count(0)

#普通切片方法,报错
print(c[10:20])


#使用itertools.islice()

import itertools

for x in itertools.islice(c, 10, 20):
    print(x)
'''
10
11
12
13
14
15
16
17
18
19
'''
           
迭代器和生成器不能使用标准的切片操作,因为它们的长度事先我们并不知道 (并且也没有实现索引)。函数 islice() 返回一个可以生成指定元素的迭代器,它通过遍历并丢弃直到切片开始索位置的所有元素。然后才开始一个个的返回元素,并直到切片结束索引位置。

跳过可迭代对象的开始部分

使用 itertools.dropwhile() 跳过

例:

#假定你在读取一个开始部分是几行注释的源文件

with open('/etc/passwd') as f:
    for line in f:
    print(line, end='')

##
# User Database
#
# Note that this file is consulted directly only when the system is running
# in single-user mode. At other times, this information is provided by
# Open Directory
...
##
nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false
root:*:0:0:System Administrator:/var/root:/bin/sh

#如果你想跳过开始部分的注释行的话,可以这样做:

from itertools import dropwhile

with open('/etc/passwd') as f:
    for line in dropwhile(lambda line: line.startswith('#'), f):
        print(line, end='')
           
使用 itertools.islice() 跳过
如果你已经明确知道了要跳过的元素的个数的话,可以用此方法

例:

from itertools import islice

#定义一个列表
items = ['a', 'b', 'c', 1, 4, 10, 15]

#使用islice(),并遍历,指定从第3个元素到最后一个元素(即None)
for x in islice(items, 3, None):
    print(x)
'''
1
4
10
15
'''
           
在这个例子中, islice() 函数最后那个 None 参数指定了你要获取从第 3 个到最后的所有元素,如果 None 和 3 的位置对调,意思就是仅仅获取前三个元素恰恰相反,(这个跟切片的相反操作 [3:] 和 [:3] 原理是一样的)

排列组合的迭代

如何迭代遍历一个集合中元素的所有可能的排列或组合?
使用 itertools.permutations() 迭代遍历

例:

#定义一个列表
mlist = ['a', 'b', 'c']

from itertools import permutations

for i in permutations(mlist):
    print(i)
'''
('a', 'b', 'c')
('a', 'c', 'b')
('b', 'a', 'c')
('b', 'c', 'a')
('c', 'a', 'b')
('c', 'b', 'a')
'''

#如果想得到指定长度的所有排列,还可以传递一个可选的长度参数

for i in permutations(mlist,2):
    print(i)
'''
('a', 'b')
('a', 'c')
('b', 'a')
('b', 'c')
('c', 'a')
('c', 'b')
'''
           
使用 itertools.combinations() 迭代遍历
此方法可得到输入集合中元素的所有的组合,并且不介意顺序,如:(a,b)和(b,a)只会输出一个。

例:

mlist = ['a','b','c']

from itertools import combinations

for c in combinations(mlist, 2):
    print(c)
    
'''
('a', 'b')
('a', 'c')
('b', 'c')
'''
           
使用 tertools.combinations with replacement() 迭代遍历
在计算组合的时候,一旦元素被选取就会从候选中剔除掉(比如如果元素’a’已经被选取了,那么接下来就不会再考虑它了)。而此函数允许同一个元素被选择多次

例:

from itertools import combinations_with_replacement

mlist = ['a', 'b', 'c']

for c in combinations_with_replacement(mlist, 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')
'''
           

序列上索引值迭代

使用 enumerate() 进行索引值迭代

例:

mlist = ['a', 'b', 'c']

for x, y in enumerate(mlist):
    print(x, y)
    
# 遍历的元素带有序号,序号从“0”开始
'''
0 a
1 b
2 c
'''

#想让序号从“1”开始,可以输入参数

for x, y in enumerate(mlist,1):
    print(x, y)
'''
1 a
2 b
3 c
'''
           

该注意的地方:

有时候当你在一个已经解压后的元组序列上使用 enumerate() 函数时很容易调入陷阱

例:

data = [ (1, 2), (3, 4), (5, 6), (7, 8) ]

# 正确的遍历方式
for n, (x, y) in enumerate(data):
    print(n,(x,y))
'''
0 (1, 2)
1 (3, 4)
2 (5, 6)
3 (7, 8)
'''

# 错误的遍历方式
for n, x, y in enumerate(data):
    print(n,x,y)
           

同时迭代多个序列

使用 zip() 进行迭代
此法用于同时迭代多个序列,并且每次分别从一个序列中取一个元素,zip(a, b) 会生成一个可返回元组 (x, y) 的迭代器,其中 x 来自 a,y 来自 b。一旦其中某个序列到底结尾,迭代宣告结束。因此迭代长度跟参数中最短序列长度一致。

例:

mlist = [1,3,5,7,9]
ylist = [0,2,4,6,8,10]
for x,y in zip(ylist,mlist):
    print(x,y)
'''
0 1
2 3
4 5
6 7
8 9
'''

           
使用 itertools.zip longest() 进行迭代

例:

from itertools import zip_longest

for i in zip_longest(ylist,mlist):
    print(i)

'''
(0, 1)
(2, 3)
(4, 5)
(6, 7)
(8, 9)
(10, None)
'''

#如果想把None,用其他替换,可以用fillvalue参数
for i in zip_longest(ylist,mlist,fillvalue='empty'):
    print(i)
'''
(0, 1)
(2, 3)
(4, 5)
(6, 7)
(8, 9)
(10, 'empty')
'''
           

按顺序迭代多个序列

使用 itertools.chain() 迭代

例:

from itertools import chain

a = [1, 2, 3, 4]
b = ['x', 'y', 'z']
for x in chain(a, b):
    print(x)
'''
1
2
3
4
x
y
z
'''
           

比对:itertools.chain() 要比先将序列合并再迭代要高效的多。

# 有效地执行遍历
for x in a + b:
...
# 更优的执行遍历
for x in chain(a, b):
           

创建数据处理管道

当有大量的数据需要处理,但是不能将它们一次性放入内存中时,生成器函数是一个实现管道机制的好办法

例:

#假如有个待处理的一个非常大的日志文件目录
foo/
access-log-012007.gz
access-log-022007.gz
access-log-032007.gz
...
access-log-012008
bar/
access-log-092007.bz2
...
access-log-022008

#假设每个日志文件包含这样的数据:
124.115.6.12 - - [10/Jul/2012:00:18:50 -0500] "GET /robots.txt ..." 200 71
210.212.209.67 - - [10/Jul/2012:00:18:51 -0500] "GET /ply/ ..." 200 11875
210.212.209.67 - - [10/Jul/2012:00:18:51 -0500] "GET /favicon.ico ..." 404 369
61.135.216.105 - - [10/Jul/2012:00:20:04 -0500] "GET /blog/atom.xml ..." 304 -

#为了处理这些文件,可以定义一个由多个执行特定任务独立任务的简单生成器函数组成的容器

import bz2,gzip,os,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 from it
		
#在序列的序列中寻找正则表达式
def gen_grep(pattern, lines):
	pat = re.compile(pattern)
	for line in lines:
		if pat.search(line):
			yield line

#现在可以很容易的将这些函数连起来创建一个处理管道。比如,为了查找包含单词 python 的所有日志行
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 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]
print(flatten(items),type(flatten(items)))

for x in flatten(items):
    print(x)

'''
1
2
3
4
5
6
7
8
'''
           

使用 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
           

继续阅读