天天看點

第四章:疊代器與生成器第一節:關于疊代器

文章目錄

  • 第一節:關于疊代器
      • ==手動周遊疊代器==
          • 使用 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
           

繼續閱讀