python——聊聊iterable,sequence和iterators


 

---------------------------------------------------------------前言------------------------------------------------------------------------------

  • iterable被認為是一類對象,這類對象能夠一次返回它的一個成員(也就是元素)。是適合迭代的對象。
  • 實際上,任何具有__iter__()或__getitem__()方法的對象,Python就認為它是一個iterable。
  • Python大量內置的iterable類型,如: list,str,tuple,dict,file,xrange等。使用內置的iter()函數來生成iterator。iter(iterable)   ->  iterator object
  • Sequence的字面意思是序列。既然是序列,那么就應該有成員,成員之間是有序的且包含了若干成員。
  • sequence首先是iterable。如果這個iterable可以通過整數索引來訪問其元素並可以獲
    得其大小,那么它是sequence。
  • iterator對象就是且必須實現了迭代協議(iterator protocol【1】【2】)的對象。Python里的iterator實現了兩個方法:

      __iter__()      # 返回迭代器本身

      __next__()    # Python2使用next()

  • iterable: 至少定義了__iter__()或__getitem__()方法的對象。
  • sequence: 至少定義了__len__()或者__getitem__()方法的對象。
  • iterator:至少定義__iter__()和__next__()法的對象。

--------------------------------------------------------------正文------------------------------------------------------------------------------

想必Python的老鳥們對於Python里的iterable,sequence和iterators應該非常了解,且能做到運用自如。
但是對於剛剛開始學習Python的新手們來說,這三個玩意兒估計夠讓他們抓狂的。先看一段有趣的代碼:

  • >>> l = ['a', 'b', 'c']

  • >>> l_iter = l.__iter__()

  • >>> l

  • ['a', 'b', 'c']

  • >>> l_iter

  • <list_iterator object at 0x7f40f2c46668>

  • >>> for e in l: print(e)

  • ...

  • a

  • b

    c

  • >>> for e in l: print(e)

  • ...

  • a

  • b

  • c

  • >>> l_iter.__next__()

  • 'a'

  • >>> l_iter.__next__()

  • 'b'

  • >>> for e in l_iter: print(e)

  • ...

  • c

  • >>> l_iter.__next__()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    StopIteration

  • >>> list(l_iter)

  • []

從這段代碼,可以看出一些特點:

  • list和由list生成的list_iterator都可以進行遍歷。

  • list可以無限次的使用,而list_iterator只能使用一次。

  • list_iterator通過__next__()方法來獲取下一個元素,且只能獲取一次。

  • 當最后一個元素被獲取后,如果想繼續,StopIteration將會拋出

其實在這里,list就是iterable(也是sequence),而l_iter則是iterator,為什么它們會有那些特點呢?下面將

詳細介紹它們及其之間的關系。

 

  • Iterable

在Python里iterable被認為是一類對象,這類對象能夠一次返回它的一個成員(也就是元素)。抽象一點就是
適合迭代的對象。實際上,任何具有__iter__()或__getitem__()方法的對象,Python就認為它是一個iterable。
Python里有大量內置的iterable類型,如: list,str,tuple,dict,file,xrange等。使用內置的iter()函數來生成
iterator。即:
iter(iterable)   ->  iterator object

  • Sequence

 

Sequence的字面意思是序列。既然是序列,那么就應該有成員,成員之間是有序的且包含了若干成員。當然
從理論上講,序列的成員數可以是無限制的。那么將其抽象成數據類型后,便可定義為有序對象的集合。維基
百科是這樣定義sequence的:
A sequence is an ordered list. Like a set, it contains members (also called elements, or terms).
The number of ordered elements
(possibly infinite) is called the length of the sequence.
從定義可以看出sequence有三個特性:

  • 包含成員(元素)

  • 有序成員(有序)

  • 成員數量(大小)

Python里這樣定義sequence:

An iterable which supports efficient element access using integer indices via the __getitem__()
special method and defines a
__len__() method that returns the length of the sequence.
從定義里可以看出,sequence首先是iterable。如果這個iterable可以通過整數索引來訪問其元素並可以獲
得其大
小,那么它是sequence很顯然,dict是iterable,但不是sequence,即使它有__getitem__() 和 __len__()
這兩種方法。因為它不是通過整數索引而是通過key來獲取元素的。Python將sequence對象的三個特性變成了兩
個方法__getitem__()和 __len__()。通過這兩個方法我們可以獲得sequence對象的元素和大小。

 

  • Iterator

 

Iterator(迭代器)存在於眾多面向對象的程序設計語言中,它是一種經典的設計模式。迭代器模式提供一種訪問
有序訪問聚合對象里元素的方法。具體到Python語言里,正如在我的前一篇博文《Iterators詳解》介紹的,iterator
對象就是且必須實現了迭代協議(iterator protocol【1】【2】)的對象。Python里的iterator實現了兩個方法:

  • __iter__()      # 返回迭代器本身

  • __next__()    # Python2使用next() 

通過iterable, sequence和iterator的定義可以看出這三者的密切關系。下圖很好的詮釋了它們之間的關系。

很顯然,Python認為sequence和iterator都是iterable。當然還有一部分non-sequence也被認為是iterable。例如,
dictionary,set等。定義了__getitem__()和__len__()方法的non-sequence對象和sequence對象都可以用iter()來
返回一個iterator。iter(X)是這樣工作的(X是一個iterable):

  • 調用X的__iter__()方法來生成一個iterator。

  • 如果X沒有__iter__()方法,Python將會自動構建一個iterator,並嘗試通過X[0], X[1], X[2] ... 或X[key1],x[key2] ... 來獲取元素。

Iterable里,sequence和non-sequence的區別就在於訪問元素的方式,通過整數索引(index)訪問的是seqence,
而不能通過整數索引訪問的是non-sequence。其實,我們完全可以通過對象定義的方法來區分一個對象到底是iterable,
sequence還是iterator。

  • iterable: 至少定義了__iter__()或__getitem__()方法的對象。

  • sequence: 至少定義了__len__()或者__getitem__()方法的對象。

  • iterator:至少定義__iter__()和__next__()法的對象。

下面來看一些實例:

  • #!/usr/bin/env python

  • # -*- coding: utf -*-

     

  • class Train(object):

  •  

  • def __init__(self, cars):

  •         self.cars = cars

     

  • def __len__(self):

  • return self.cars

     

  • def __getitem__(self, key):

  • if key >= 0:

  •             index = key

    else:

  •             index = self.cars + key

  • if 0 <= index < len(self):

  • return 'Carriage #%s' % (index + 1)

    else:

  • raise IndexError('No carriage at #%s' % key)

這里,我們定義了一個名為Train的類實現了一個火車的sequence。這個實現了sequence的兩個關鍵的方法:

__len__()和__getitem__(),實際上這兩個方法被叫作sequence protocol。實現了這兩個方法,也就實現了

個immutable 【4】的seqence。

  • >>> from train import Train

  • >>> t = Train(4)

  • >>> t

  • <train.Train object at 0x7f491e457080>

    >>> t[0]

  • 'Carriage #1'

  • >>> len(t)

    4

  • >>> t.__len__()

  • 4

  • >>> t.__getitem__(2)

  • 'Carriage #3'

  • >>> t[3]

  • 'Carriage #4'

  • >>> t[-3]
    'Carriage #2'

  • >>> for i in t:
    ...     print(i)
    ...
    Carriage #1
    Carriage #2
    Carriage #3
    Carriage #4

從上面的實驗代碼可以看出,我們可以對 t 進行遍歷,獲取元素和大小毫無疑問,這個t對象有__getitem__()

方法,所以它也是iterable。我們可以將用t來生成新的list,tuple等對象,也可生成iterator對象。請看下面的代碼:

  • >>> list(t)

  • ['Carriage #1', 'Carriage #2', 'Carriage #3', 'Carriage #4'] # 生成了一個list對象

  • >>> iter(t)

  • <iterator object at 0x7f491e4755f8>

  • >>> tuple(t)

  • ('Carriage #1', 'Carriage #2', 'Carriage #3', 'Carriage #4') # 生成了一個tuple對象

  • >>> t_iter = iter(t)          
    >>> t_iter
    <iterator object at 0x7f491e475da0>                          # 生成了一個iterator對象
    >>> "__iter__" and "__next__" in dir(t_iter) #
    True
    >>>

那么iterator和iterable(非iterator的iterable)到底有什么區別呢?我先看下面的代碼:

  • >>> t

  • <train.Train object at 0x7f491e457080>

  • >>> t[0]

  • 'Carriage #1'

  • >>> t[3]

  • 'Carriage #4'

  • >>> t[4]

  • Traceback (most recent call last):

  •   File "<stdin>", line 1, in <module>

  •   File "/home/pygeek/DEV/pyprj/reading/train.py", line 22, in __getitem__

  • raise IndexError('No carriage at #%s' % key)

  • IndexError: No carriage at #4

  • >>> t[2]

  • 'Carriage #3'

  • >>> t[1]

  • 'Carriage #2'

  • >>> for i in t:             # 第一次遍歷

  • ... print(i)

  • ...

  • Carriage #1

  • Carriage #2

  • Carriage #3

  • Carriage #4

  • >>> for i in t:             # 第二次遍歷

    ...     print(i)

    ...

    Carriage #1

    Carriage #2

    Carriage #3

    Carriage #4

t 對象是sequence,當然它也是iterable,但是它不是iterator。我們通過索引或遍歷訪問元素。當索引超出t對象

的長度范圍時,IndexError將會被拋出。t對象可以被多次甚至無限次的使用但是,iterator確實有些不一樣了。

  • >>> t_iter = iter(t)

  • >>> t_iter

  • <iterator object at 0x7f491c9ac4e0>

  • >>> t_iter[0]

    Traceback (most recent call last):

  •   File "<stdin>", line 1, in <module>

  • TypeError: 'iterator' object is not subscriptable

  • >>> t_iter.__next__()

  • 'Carriage #1'

  • >>> t_iter.__next__()

  • 'Carriage #2'

  • >>> t_iter.__next__()

  • 'Carriage #3'

  • >>> t_iter.__next__()

  • 'Carriage #4'

  • >>> t_iter.__next__()

  • Traceback (most recent call last):

  •   File "<stdin>", line 1, in <module>

  • StopIteration

  • >>> for i in t_iter:

  • ... print(i)

  • ...

  • >>>

上面的代碼說明,iterator不能通過索引來訪問元素,但是可以用它的__next__()方法來訪問元素;可進行遍歷,

但是只能完整遍歷一次。當所有的元素都被訪問過后,再調用__next__(),拋出的異常是StopIteration,而不是

IndexError。此時,我們稱iterator已經被耗盡,不能再進行遍歷。實際上,iterator存儲了元素的位置狀態並可以

通過調用__next__()訪問下一個元素並更新內部的位置狀態直到所有的元素都被訪問。這些不同使得iterator對象

會比list,tuple這樣的iterable更有效率並占用更少的內存空間。我們可以用sys.getsizeof()來做一粗略的測試:

  • >>> t_iter = iter(t)

  • >>> t_iter

  • <iterator object at 0x7f491e479780>

  • >>> t_list = list(t)

  • >>> t_list

  • ['Carriage #1', 'Carriage #2', 'Carriage #3', 'Carriage #4']

  • >>> t_tuple = tuple(t)

  • >>> t_tuple

  • ('Carriage #1', 'Carriage #2', 'Carriage #3', 'Carriage #4')

  • >>> sys.getsizeof(t), sys.getsizeof(t_iter), sys.getsizeof(t_list), sys.getsizeof(t_tuple)

  • (56, 56, 120, 80)

我們可以看到list對象比iterator對象的兩倍還要大。所以,如果我們僅僅只是需要iterable的元素,而不需要一個

完整的list的話,那么用iterator將更有效率並節省空間。

-------------------------------------------------------------------后記------------------------------------------------------------------

簡單總結一下:

iterable: 實現了__iter__()或__getitem__()方法的對象。

sequence:實現是了sequence protocol(即方法: __getitem__()和__len__()),並能使用整數索引訪問元素的iterable對象。

iterator: 實現了iterator protocol(即方法:__next__()和__iter__())的iterable對象。

通過iterable創建iterator的方法:

iter(iterable) -> iterator 

iterable.__iter__()  -> iterator 【5】

  • 注:

 

【1】所有的代碼都在Python 3.4.0下測試通過。
【2】protocol: a synonym  for interface used in dynamic languages like Python,Ruby,...

【3】iterator protocol: The iterator protocol consists of two methods. The __iter__() method, which must return the iterator object and the __next__() method, which returns the next element.
【4】immutable: An object with a fixed value. Such an object cannot be altered. A new object has to be created if a different value has to be stored.
【5】iterable.__iter__()返回的是一個新的iterator,而iterator.__iter__()返回的iterator對象本身。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM