可迭代對象和迭代器是兩種不同的數據類型,它們都在我們的編程中時常可以遇到。當然他們之間也有很大的關聯,接下來就讓我們把它們搞定。
1.迭代器(Iterator)
迭代器表示的是一個數據流,並不表示一個數據實體,我們可以使用next()方法計算下一個數據。或者說,可以使用next()方法的就是迭代器。
生成器是迭代器,生成器可以使用下面這種方式生成。下面請看具體實例
a = (i for i in range(10))
print(next(a)) #output:0
print(next(a)) #output:1
print(a[4])
# error:TypeError: 'generator' object is not subscriptable
如代碼所示,使用next()方法可以取出生成器的值,但是使用類似於list的切片便會報錯,所以說,生成器不是一個數據實體,而是一個數據流,只能按順序取出數據。
那如何用代碼判斷這到底是不是迭代器呢?我們可以使用isinstance關鍵字
import collections
a = (i for i in range(10))
print(type(a))
print(isinstance(a,collections.Iterator))
<class 'generator'>
True
可以看到生成器確實是迭代器。
生成器函數也是迭代器,即使用yield定義的類函數體,下面可以驗證一下。
import collections
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
return 'done'
f = fib(8)
print(type(f))
print(isinstance(f,collections.Iterator))
<class 'generator'>
True
2.可迭代對象(Iterable)
可迭代對象指的就是可以迭代的對象,或者說可以作用於for循環的都是可迭代對象。那這就包括list,tuplt,dict,set等類型,也包括上面講的生成器。是的,它們都可以用於for循環。
a = [1,2,3,4,5]
for x in a:
print(x)
這對list可以順利執行,同樣,對於其他序列也是同樣正確的。
那接下來我們判斷一下到底list,set,dict,tuple是不是可迭代類型。判斷是不是可迭代類型可以使用isinstance和Iterable對象。
import collections
a = [1,2,3]
print(type(a))
print(isinstance(a,collections.Iterable))
# output:
<class 'list'>
True
可以看到,list確實是可迭代類型。
3.兩者之間的關係
事實上,Iterator是Iterable的子類型,Iterator是從Iterable繼承下來的。
查看源碼可以看到這一點,值得注意的是,next()方法內部正是用__next__()實現的。
class Iterator(Iterable):
__slots__ = ()
@abstractmethod
def __next__(self):
'Return the next item from the iterator. When exhausted, raise StopIteration'
raise StopIteration
def __iter__(self):
return self
因此,兩者之間關係已經很明了了,迭代器肯定是可迭代對象,但可迭代對象不一定是迭代器。
4.動手實現一個迭代器
我們自己動手寫一個有助於我們從原理層面理解迭代器,下面我便帶著大家看看迭代器如何實現。
從前面我們展示出來的迭代器的部分源碼,我們可以看出來,迭代器首先是繼承了Iterable,其次更重要的是迭代器實現了兩個虛擬方法,一個是__next__(self),另一個是__iter__(self)方法,其中__iter__(self)是從Iterable中繼承下來的。因此實現一個屬於自己的迭代器的關鍵是要實現這兩個方法。
class Fib:
def __init__(self):
self.prev = 0
self.curr = 1
def __iter__(self):
return self
def __next__(self):
value = self.curr
self.curr += 1
self.prev = value
return value
上面這段代碼實現了一個簡單的從1開始的遞增序列的一個迭代器,其中__iter__(self)返回迭代器本身,而__next__(self)實現了如何去取得下一個值。
那麼接下來我們來取出下一個值,驗證一下我們的迭代器正不正確。
f = Fib()
print(next(f))
print(next(f))
print(f.__next__())
print(f.__next__())
# output:
1
2
3
4
結果正是我們當初設計的迭代器一樣。這裡使用了next()函數,也用了__next__()方法,其實本質是一樣的。只不過next()包裝了__next__()方法,是一個全局的函數,而__next__()是一個對象的方法,只能由對象調用。
這樣的方法在Python中還有許多,例如我們常用的len()函數,就是這樣的設計原理,在對象內部有一個__len__()方法,調用len(),內部實現的就是__len__().
s = 'abcdefg'
print(s.__len__()) #output:7
print(len(s)) #output:7
如代碼所示,有兩種方法可以求取長度。我們在實際寫代碼中通常直接使用len()方法,因為這個調用起來更簡單,可讀性也更加好,這也是為什麼Python要提供len()方法的原因,畢竟Python的設計是遵循優雅簡潔的原則進行的。
注意:在運行上面代碼時,不要將迭代器對象轉化為list或者set等數據結構,也就是不要執行list(f),這會使得這個代表著無限序列的迭代器一次性裝入內存。我因為不小心執行了這個代碼,導致內存使用率到達90%多,最後重啟電腦才恢復。
最後
那為什麼需要迭代器呢?那是因為我們的內存是有限的,但可能需要表示的數據是無限的,那這個時候我們可以使用迭代器,在Python3.x中,以前所有返回序列的方法,都已經變為返回迭代器了。我們可以將迭代器看作一個懶惰的傢伙,他只有會在要使用時才會給你計算和提供一個數據,這可以讓我們更省內存,運行效率更高。
推薦閱讀:
Ajax網頁爬取案例詳解
用python來分析一波股票
日常學python
代碼不止bug,還有美和樂趣