天天看點

疊代器與生成器 中

疊代器切片

問題

你想得到一個由疊代器生成的切片對象,但是标準切片操作并不能做到。

解決方案

函數 itertools.islice() 正好适用于在疊代器和生成器上做切片操作。
>>> def count(n):
...     while True:
...         yield n
...         n += 1
...
>>> c = count(0)
>>> c[10:20]
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
TypeError: 'generator' object is not subscriptable

>>> # 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() 會消耗掉傳入的疊代器中的資料。 必須考慮到疊代器是不可逆的這個事實。 是以如果你需要之後再次通路這個疊代器的話,那你就得先将它裡面的資料放入一個清單中。

跳過可疊代對象的開始部分

問題

你想周遊一個可疊代對象,但是它開始的某些元素你并不感興趣,想跳過它們。

解決方案

itertools 子產品中有一些函數可以完成這個任務。 首先介紹的是 itertools.dropwhile() 函數。使用時,你給它傳遞一個函數對象和一個可疊代對象。 它會傳回一個疊代器對象,丢棄原有序列中直到函數傳回Flase之前的所有元素,然後傳回後面所有元素。

為了示範,假定你在讀取一個開始部分是幾行注釋的源檔案。比如:

>>> 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='')
...
nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false
root:*:0:0:System Administrator:/var/root:/bin/sh
...
>>>           

這個例子是基于根據某個測試函數跳過開始的元素。 如果你已經明确知道了要跳過的元素的個數的話,那麼可以使用 itertools.islice() 來代替。比如:

>>> from itertools import islice
>>> items = ['a', 'b', 'c', 1, 4, 10, 15]
>>> for x in islice(items, 3, None):
...     print(x)
...
1
4
10
15
>>>
在這個例子中, islice() 函數最後那個 None 參數指定了你要擷取從第3個到最後的所有元素, 如果 None 和3的位置對調,意思就是僅僅擷取前三個元素恰恰相反, (這個跟切片的相反操作 [3:] 和 [:3] 原理是一樣的)。           

排列組合的疊代

你想疊代周遊一個集合中元素的所有可能的排列或組合

解決方案

itertools子產品提供了三個函數來解決這類問題。 其中一個是

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

使用 itertools.combinations() 可得到輸入集合中元素的所有的組合。比如:

>>> 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',)
>>>```
對于 combinations() 來講,元素的順序已經不重要了。 也就是說,組合 ('a', 'b') 跟 ('b', 'a') 其實是一樣的(最終隻會輸出其中一個)。

在計算組合的時候,一旦元素被選取就會從候選中剔除掉(比如如果元素’a’已經被選取了,那麼接下來就不會再考慮它了)。 而函數 itertools.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')
>>>
>           

序列上索引值疊代

問題

你想在疊代一個序列的同時跟蹤正在被處理的元素索引。

解決方案

内置的 enumerate() 函數可以很好的解決這個問題:

内置的 enumerate() 函數可以很好的解決這個問題:

>>> my_list = ['a', 'b', 'c']
>>> for idx, val in enumerate(my_list):
...     print(idx, val)
...
0 a
1 b
2 c           

為了按傳統行号輸出(行号從1開始),你可以傳遞一個開始參數:

>>> my_list = ['a', 'b', 'c']
>>> for idx, val in enumerate(my_list, 1):
...     print(idx, val)
...
1 a
2 b
3 c           

這種情況在你周遊檔案時想在錯誤消息中使用行号定位時候非常有用:

def parse_data(filename):
    with open(filename, 'rt') as f:
        for lineno, line in enumerate(f, 1):
            fields = line.split()
            try:
                count = int(fields[1])
                ...
            except ValueError as e:
                print('Line {}: Parse error: {}'.format(lineno, e))           

enumerate() 對于跟蹤某些值在清單中出現的位置是很有用的。 是以,如果你想将一個檔案中出現的單詞映射到它出現的行号上去,可以很容易的利用 enumerate() 來完成:

word_summary = defaultdict(list)

with open('myfile.txt', 'r') as f:
    lines = f.readlines()

for idx, line in enumerate(lines):
    # Create a list of words in current line
    words = [w.strip().lower() for w in line.split()]
    for word in words:
        word_summary[word].append(idx)
           

同時疊代多個序列

問題

你想同時疊代多個序列,每次分别從一個序列中取一個元素。

解決方案

為了同時疊代多個序列,使用 zip() 函數。比如

```bash

xpts = [1, 5, 4, 2, 10, 7]

ypts = [101, 78, 37, 15, 62, 99]

for x, y in zip(xpts, ypts):

... print(x,y)

...

1 101

5 78

4 37

2 15

10 62

7 99

zip(a, b) 會生成一個可傳回元組 (x, y) 的疊代器,其中x來自a,y來自b。 一旦其中某個序列到底結尾,疊代宣告結束。 是以疊代長度跟參數中最短序列長度一緻。

a = [1, 2, 3]

b = ['w', 'x', 'y', 'z']

for i in zip(a,b):

... print(i)

...

(1, 'w')

(2, 'x')

(3, 'y')

如果這個不是你想要的效果,那麼還可以使用 itertools.zip_longest() 函數來代替。比如:

from itertools import zip_longest

for i in zip_longest(a,b):

... print(i)

...

(1, 'w')

(2, 'x')

(3, 'y')

(None, 'z')

for i in zip_longest(a, b, fillvalue=0):

... print(i)

...

(1, 'w')

(2, 'x')

(3, 'y')

(0, 'z')

```

最後強調一點就是, zip() 會建立一個疊代器來作為結果傳回。 如果你需要将結對的值存儲在清單中,要使用 list() 函數。比如:

zip(a, b)

<zip object at 0x1007001b8>

list(zip(a, b))

[(1, 10), (2, 11), (3, 12)]