Python 旋風之旅—迭代器

迭代器

通常,數據分析重要的部分是以自動化的方式一遍又一遍地重複類似的計算。 例如,您可能有一張名單表,要將其分割成名字和姓氏,或者是日期表要將其轉換成某種標準格式的日期。 Python 給出的解決方案一是迭代器語法。 我們已經通過 range 迭代器看到了這一點:

for i in range(10):
    print(i, end=' ')
0 1 2 3 4 5 6 7 8 9 

在這裏,我們將進行更深入的研究。 事實上,在Python 3中,range 不是一個列表,而是一個稱爲迭代器的東西,瞭解它的工作方式是理解各種非常有用的Python功能的關鍵。

 

遍歷列表

在遍歷列表的具體情景下,最容易理解迭代器。 考慮以下:

for value in [2, 4, 6, 8, 10]:
    # do some operation
    print(value + 1, end=' ')
3 5 7 9 11 

熟悉的“ for x in y”語法使我們可以對列表中的每個值重複執行某些操作。 該代碼的語法非常接近其英語描述("for [each] value in [the] list"),這只是使Python成爲一種易於學習和使用的直觀語言的語法選擇之一。

但是,表面上語句的行爲並不是真正發生的事情。 當您編寫像"for val in L"的內容時,Python解釋器會檢查它是否具有迭代器接口,自己可以使用內置的iter 函數檢查:

iter([2, 4, 6, 8, 10])
<list_iterator at 0x104722400>

正是此迭代器對象提供了for循環所需的功能。 iter對象是一個容器,它使您可以在下一個對象有效時對其進行訪問,可以通過內置函數 next 看到該對象:

I = iter([2, 4, 6, 8, 10])
print(next(I))
2
print(next(I))

4

print(next(I))

6

 

這種間接設計有什麼目的呢? 然而,事實證明這是非常有用的,因爲它允許 Python 將事物視爲實際上不是列表的列表。

 

range():像列表並不總是列表

這種間接迭代的最常見示例可能是 Python 3 中的 range() 函數(在Python 2中名爲 xrange()),該函數不返回列表,而是返回一個特殊的range() 對象:

range(10)
range(0, 10)

range(像列表一樣)向外公開了一個迭代器:

iter(range(10))
<range_iterator at 0x1045a1810>

因此,Python知道將其視爲列表:

for i in range(10):
    print(i, end=' ')
0 1 2 3 4 5 6 7 8 9

間接迭代器的好處是,不會顯式創建完整列表! 我們可以通過 range 計算來看到這一點,如果我們實際實例化了它,將會耗盡我們的系統內存(請注意,在Python 2中,range 創建了一個列表,因此運行以下代碼情況會非常糟糕!):

N = 10 ** 12
for i in range(N):
    if i >= 10: break
    print(i, end=', ')
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 

如果 range 實際上創建了一個1萬億個值的列表,那麼它將佔用數十TB的機器內存:這是一種浪費!考慮到我們忽略了除前10個值之外的所有值。

實際上,根本沒有理由迭代器必須結束! Python的 itertools 庫包含一個充當無限範圍的count函數:

from itertools import count

for i in count():
    if i >= 10:
        break
    print(i, end=', ')
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 

如果在這裏我們沒有循環中斷 break 語句,那麼它將繼續愉快地計數,直到該過程被手動中斷或終止(例如使用ctrl-C)爲止。

 

有用的迭代器

這種迭代器語法幾乎在 Python 內置類型以及更多數據科學特定對象中廣泛使用,我們將在後面的部分中探討。 在這裏,我們將介紹 Python 語言中一些更有用的迭代器:

 

enumerate

通常,您不僅需要迭代數組中的值,而且還需要跟蹤索引。 您可能會想這樣做:

L = [2, 4, 6, 8, 10]
for i in range(len(L)):
    print(i, L[i])
0 2
1 4
2 6
3 8
4 10

儘管這確實可行,但是 Python 提供的 enumerate 迭代器使語法更簡潔:

for i, val in enumerate(L):
    print(i, val)
0 2
1 4
2 6
3 8
4 10

這是枚舉列表中索引和值的更“ Pythonic”方式。

 

zip

在其他時候,您可能要同時迭代多個列表。 您當然可以像我們之前看過的非Pythonic示例那樣遍歷索引,但是最好使用zip迭代器,它將可迭代的項鍊在一起:

L = [2, 4, 6, 8, 10]
R = [3, 6, 9, 12, 15]
for lval, rval in zip(L, R):
    print(lval, rval)
2 3
4 6
6 9
8 12
10 15

可以將任意數量的可迭代項鍊在一起,如果長度不同,則最短者將確定拉鍊的長度。

 

map 和 filter

map 迭代器接收一個函數並將其應用於迭代器中的值:

# find the first 10 square numbers
square = lambda x: x ** 2
for val in map(square, range(10)):
    print(val, end=' ')
0 1 4 9 16 25 36 49 64 81 

filter 迭代器看起來很相似,除了filter 函數返回 True,filter 迭代器纔會傳遞值:

# find values up to 10 for which x % 2 is zero
is_even = lambda x: x % 2 == 0
for val in filter(is_even, range(10)):
    print(val, end=' ')
0 2 4 6 8 

map和filter函數以及reduce函數(位於Python的functools模塊中)是函數式編程風格的基本組成部分,儘管在Python世界中不是占主導地位的編程風格,但它具有直言不諱的支持者(例如,請參見 ,pytoolz庫)。

 

迭代器作爲函數參數

我們在 *args和**kwargs:靈活的參數 中看到了。 * args和** kwargs可用於將序列和字典傳遞給函數。 事實證明,* args語法不僅適用於序列,而且適用於任何迭代器:

print(*range(10))
0 1 2 3 4 5 6 7 8 9

因此,例如,我們可以使用一個小技巧將前面的map示例壓縮爲以下內容:

print(*map(lambda x: x ** 2, range(10)))
0 1 4 9 16 25 36 49 64 81

使用這個技巧,我們可以回答Python學習者論壇中一個古老的問題:爲什麼沒有 unzip()  與 zip() 功能相反的函數呢? 如果您將自己鎖在一個黑暗的壁櫥中並考慮了一會兒,您可能會意識到 zip() 的反面就是... zip()! 關鍵是 zip() 可以將任意數量的迭代器或序列鏈在一起。 觀察:

L1 = (1, 2, 3, 4)
L2 = ('a', 'b', 'c', 'd')
z = zip(L1, L2)
print(*z)
(1, 'a') (2, 'b') (3, 'c') (4, 'd')
z = zip(L1, L2)
new_L1, new_L2 = zip(*z)
print(new_L1, new_L2)
(1, 2, 3, 4) ('a', 'b', 'c', 'd')

思考一會。 如果您瞭解它爲什麼起作用,那麼您已經很深入的理解Python迭代器了!

 

專用迭代器:itertools

我們簡要介紹了無限範圍迭代器itertools.count。 itertools模塊包含大量有用的迭代器。 值得您花時間探索該模塊以查看可用的功能。 例如,考慮itertools.permutations函數,該函數遍歷序列的所有排列:

from itertools import permutations
p = permutations(range(3))
print(*p)
(0, 1, 2) (0, 2, 1) (1, 0, 2) (1, 2, 0) (2, 0, 1) (2, 1, 0)

同樣,itertools.combinations函數迭代列表中N個值的所有唯一組合:

from itertools import combinations
c = combinations(range(4), 2)
print(*c)
(0, 1) (0, 2) (0, 3) (1, 2) (1, 3) (2, 3)

與乘積迭代器有關的是 product,它迭代兩個或多個可迭代對象之間的所有配對:

from itertools import product
p = product('ab', range(3))
print(*p)
('a', 0) ('a', 1) ('a', 2) ('b', 0) ('b', 1) ('b', 2)

itertools 中存在更多有用的迭代器:完整列表以及一些示例可在Python的在線文檔中找到。

 

本文來自翻譯如下文章,僅用於學習

原文:

https://nbviewer.jupyter.org/github/jakevdp/WhirlwindTourOfPython/blob/master/10-Iterators.ipynb

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章