迭代相關:__iter__函式和__next__函式
__ iter__函式和__next__函式
迭代器就是重複地做一些事情,可以簡單的理解為迴圈,在python中實現了__iter__方法的物件是可迭代的,實現了next()方法的物件是迭代器,這樣說起來有點拗口,實際上要想讓一個迭代器工作,至少要實現__iter__方法和next方法。很多時候使用迭代器完成的工作使用列表也可以完成,但是如果有很多值列表就會佔用太多的記憶體,而且使用迭代器也讓我們的程式更加通用、優雅、pythonic。
如果一個類想被用於for 。。。 in迴圈,類似list或tuple那樣,就必須實現一個__iter__()方法,該方法返回一個迭代物件,然後,Python的for迴圈就會不斷呼叫該迭代物件的next()方法拿到迴圈的下一個值,直到遇到StopIteration錯誤時退出迴圈。
容器(container):
容器是用來儲存元素的一種資料結構,容器將所有資料儲存在記憶體中,Python中典型的容器有:list,set,dict,str等等。
class
test
():
def
__init__
(
self
,
data
=
1
):
self
。
data
=
data
def
__iter__
(
self
):
return
self
def
__next__
(
self
):
if
self
。
data
>
5
:
raise
StopIteration
else
:
self
。
data
+=
1
return
self
。
data
for
item
in
test
(
3
):
(
item
)
輸出結果:
4
5
6
for … in… 這個語句其實做了兩件事。第一件事是獲得一個可迭代器,即呼叫了__iter__()函式。 第二件事是迴圈的過程,迴圈呼叫__next__()函式。
對於test這個類來說,它定義了__iter__和__next__函式,所以是一個可迭代的類,也可以說是一個可迭代的物件(Python中一切皆物件)。
迭代器:
含有__next__()函式的物件都是一個迭代器,所以test也可以說是一個迭代器。如果去掉__itet__()函式,test這個類也不會報錯。如下程式碼所示:
class
test
():
def
__init__
(
self
,
data
=
1
):
self
。
data
=
data
def
__next__
(
self
):
if
self
。
data
>
5
:
raise
StopIteration
else
:
self
。
data
+=
1
return
self
。
data
t
=
test
(
3
)
for
i
in
range
(
3
):
(
t
。
__next__
())
輸出結果:
4
5
6
生成器:
生成器是一種特殊的迭代器。當呼叫fib()函式時,生成器例項化並返回,這時並不會執行任何程式碼,生成器處於空閒狀態,注意這裡prev, curr = 0, 1並未執行。然後這個生成器被包含在list()中,list會根據傳進來的引數生成一個列表,所以它對fib()物件(一切皆物件,函式也是物件)呼叫__next__方法。
def
fib
(
end
=
1000
):
prev
,
curr
=
0
,
1
while
curr
<
end
:
yield
curr
prev
,
curr
=
curr
,
curr
+
prev
(
list
(
fib
()))
輸出結果:
[
1
,
1
,
2
,
3
,
5
,
8
,
13
,
21
,
34
,
55
,
89
,
144
,
233
,
377
,
610
,
987
]
上面只是做了幾個演示,這裡具體說明一下:
當呼叫iter函式的時候,生成了一個迭代物件,要求__iter__必須返回一個實現了__next__的物件,我們就可以透過next函式訪問這個物件的下一個元素了,並且在你不想繼續有迭代的情況下丟擲一個StopIteration的異常(for語句會捕獲這個異常,並且自動結束for),下面實現了一個自己的類似range函式的功能。
class
MyRange
(
object
):
def
__init__
(
self
,
end
):
self
。
start
=
0
self
。
end
=
end
def
__iter__
(
self
):
return
self
def
__next__
(
self
):
if
self
。
start
<
self
。
end
:
ret
=
self
。
start
self
。
start
+=
1
return
ret
else
:
raise
StopIteration
from
collections。abc
import
*
a
=
MyRange
(
5
)
(
isinstance
(
a
,
Iterable
))
(
isinstance
(
a
,
Iterator
))
for
i
in
a
:
(
i
)
輸出結果是:
True
True
0
1
2
3
4
接下來我們使用
next
函式模擬一次:
class
MyRange
(
object
):
def
__init__
(
self
,
end
):
self
。
start
=
0
self
。
end
=
end
def
__iter__
(
self
):
return
self
def
__next__
(
self
):
if
self
。
start
<
self
。
end
:
ret
=
self
。
start
self
。
start
+=
1
return
ret
else
:
raise
StopIteration
a
=
MyRange
(
5
)
(
next
(
a
))
(
next
(
a
))
(
next
(
a
))
(
next
(
a
))
(
next
(
a
))
(
next
(
a
))
# 其實到這裡已經完成了,我們在執行一次檢視異常
可以看見一個很明顯的好處是,每次產生的資料,是產生一個用一個,什麼意思呢,比如我要遍歷
[0, 1, 2, 3。。。。。]
一直到10億,如果使用列表的方式,那麼是會全部載入記憶體的,但是如果使用迭代器,可以看見,當用到了(也就是在呼叫了
next
)才會產生對應的數字,這樣就可以節約記憶體了,這是一種懶惰的載入方式。
總結
可以使用
collection。abs
裡面的
Iterator
和
Iterable
配合
isinstance
函式來判斷一個物件是否是可迭代的,是否是迭代器物件。
iter
實際是對映到了
__iter__
函式。
只要實現了
__iter__
的物件就是可迭代物件(
Iterable
),正常情況下,應該返回一個實現了
__next__
的物件(雖然這個要求不強制),如果自己實現了
__next__
,當然也可以返回自己。
同時實現了
__iter__
和
__next__
的是迭代器(
Iterator
),當然也是一個可迭代物件了,其中
__next__
應該在迭代完成後,丟擲一個
StopIteration
異常。
for
語句會自動處理這個
StopIteration
異常以便結束
for
迴圈。