鍍金池/ 教程/ Python/ 迭代器
標(biāo)準(zhǔn)庫 (4)
如何成為 Python 高手
標(biāo)準(zhǔn)庫 (6)
標(biāo)準(zhǔn)庫 (3)
類(2)
Pandas 使用 (2)
xml
用 tornado 做網(wǎng)站 (5)
文件(1)
練習(xí)
列表(3)
從小工到專家
除法
錯誤和異常 (2)
函數(shù)(1)
用 tornado 做網(wǎng)站 (7)
為做網(wǎng)站而準(zhǔn)備
函數(shù)練習(xí)
標(biāo)準(zhǔn)庫 (8)
Pandas 使用 (1)
回顧 list 和 str
字典(1)
用 tornado 做網(wǎng)站 (3)
字符串(1)
函數(shù)(2)
寫一個簡單的程序
將數(shù)據(jù)存入文件
語句(5)
SQLite 數(shù)據(jù)庫
集成開發(fā)環(huán)境(IDE)
集合(1)
類(1)
用 tornado 做網(wǎng)站 (6)
用 tornado 做網(wǎng)站 (2)
自省
語句(4)
錯誤和異常 (1)
用 tornado 做網(wǎng)站 (4)
集合(2)
列表(1)
標(biāo)準(zhǔn)庫 (1)
生成器
mysql 數(shù)據(jù)庫 (1)
第三方庫
實(shí)戰(zhàn)
運(yùn)算符
類(3)
字典(2)
語句(1)
數(shù)和四則運(yùn)算
語句(2)
文件(2)
MySQL 數(shù)據(jù)庫 (2)
電子表格
迭代器
mongodb 數(shù)據(jù)庫 (1)
特殊方法 (2)
特殊方法 (1)
字符編碼
編寫模塊
用 tornado 做網(wǎng)站 (1)
標(biāo)準(zhǔn)庫 (5)
函數(shù)(4)
類(5)
字符串(2)
關(guān)于 Python 的故事
函數(shù)(3)
字符串(4)
處理股票數(shù)據(jù)
常用數(shù)學(xué)函數(shù)和運(yùn)算優(yōu)先級
字符串(3)
為計(jì)算做準(zhǔn)備
多態(tài)和封裝
類(4)
迭代
語句(3)
錯誤和異常 (3)
分析 Hello
Python 安裝
標(biāo)準(zhǔn)庫 (2)
列表(2)
元組

迭代器

迭代,對于讀者已經(jīng)不陌生了,曾有專門一節(jié)來講述,如果印象不深,請復(fù)習(xí)《迭代》。

正如讀者已知,對序列(列表、元組)、字典和文件都可以用 iter() 方法生成迭代對象,然后用 next() 方法訪問。當(dāng)然,這種訪問不是自動的,如果用 for 循環(huán),就可以自動完成上述訪問了。

如果用 dir(list),dir(tuple),dir(file),dir(dict) 來查看不同類型對象的屬性,會發(fā)現(xiàn)它們都有一個名為__iter__的東西。這個應(yīng)該引起讀者的關(guān)注,因?yàn)樗偷鳎╥terator)、內(nèi)置的函數(shù) iter() 在名字上是一樣的,除了前后的雙下劃線。望文生義,我們也能猜出它肯定是跟迭代有關(guān)的東西。當(dāng)然,這種猜測也不是沒有根據(jù)的,其重要根據(jù)就是英文單詞,如果它們之間沒有一點(diǎn)關(guān)系,肯定不會將命名搞得一樣。

猜對了。__iter__就是對象的一個特殊方法,它是迭代規(guī)則(iterator potocol)的基礎(chǔ)。或者說,對象如果沒有它,就不能返回迭代器,就沒有 next() 方法,就不能迭代。

提醒注意,如果讀者用的是 Python3.x,迭代器對象實(shí)現(xiàn)的是__next__() 方法,不是 next()。并且,在 Python3.x 中有一個內(nèi)建函數(shù) next(),可以實(shí)現(xiàn) next(it),訪問迭代器,這相當(dāng)于于 python2.x 中的 it.next()(it 是迭代對象)。

那些類型是 list、tuple、file、dict 對象有__iter__()方法,標(biāo)著他們能夠迭代。這些類型都是 Python 中固有的,我們能不能自己寫一個對象,讓它能夠迭代呢?

當(dāng)然呢!要不然 python 怎么強(qiáng)悍呢。

#!/usr/bin/env Python
# coding=utf-8

"""
the interator as range()
"""
class MyRange(object):
    def __init__(self, n):
        self.i = 0
        self.n = n

    def __iter__(self):
        return self

    def next(self):
        if self.i < self.n:
            i = self.i
            self.i += 1
            return i
        else:
            raise StopIteration()

if __name__ == "__main__":
    x = MyRange(7)
    print "x.next()==>", x.next()
    print "x.next()==>", x.next()
    print "------for loop--------"
    for i in x:
        print i

將代碼保存,并運(yùn)行,結(jié)果是:

$ python 21401.py 
x.next()==> 0
x.next()==> 1
------for loop--------
2
3
4
5
6

以上代碼的含義,是自己仿寫了擁有 range() 的對象,這個對象是可迭代的。分析如下:

類 MyRange 的初始化方法__init__() 就不用贅述了,因?yàn)榍懊嬉呀?jīng)非常詳細(xì)分析了這個方法,如果復(fù)習(xí),請閱讀《類(2)》相關(guān)內(nèi)容。

__iter__() 是類中的核心,它返回了迭代器本身。一個實(shí)現(xiàn)了__iter__()方法的對象,即意味著其實(shí)可迭代的。

含有 next() 的對象,就是迭代器,并且在這個方法中,在沒有元素的時候要發(fā)起 StopIteration() 異常。

如果對以上類的調(diào)用換一種方式:

if __name__ == "__main__":
    x = MyRange(7)
    print list(x)
    print "x.next()==>", x.next()

運(yùn)行后會出現(xiàn)如下結(jié)果:

$ python 21401.py 
[0, 1, 2, 3, 4, 5, 6]
x.next()==>
Traceback (most recent call last):
  File "21401.py", line 26, in <module>
    print "x.next()==>", x.next()
  File "21401.py", line 21, in next
    raise StopIteration()
StopIteration

說明什么呢?print list(x) 將對象返回值都裝進(jìn)了列表中并打印出來,這個正常運(yùn)行了。此時指針已經(jīng)移動到了迭代對象的最后一個,正如在《迭代》中描述的那樣,next() 方法沒有檢測也不知道是不是要停止了,它還要繼續(xù)下去,當(dāng)繼續(xù)下一個的時候,才發(fā)現(xiàn)沒有元素了,于是返回了 StopIteration()

為什么要將用這種可迭代的對象呢?就像上面例子一樣,列表不是挺好的嗎?

列表的確非常好,在很多時候效率很高,并且能夠解決相當(dāng)普遍的問題。但是,不要忘記一點(diǎn),在某些時候,列表可能會給你帶來災(zāi)難。因?yàn)樵谀闶褂昧斜淼臅r候,需要將列表內(nèi)容一次性都讀入到內(nèi)存中,這樣就增加了內(nèi)存的負(fù)擔(dān)。如果列表太大太大,就有內(nèi)存溢出的危險(xiǎn)了。這時候需要的是迭代對象。比如斐波那契數(shù)列(在本教程多處已經(jīng)提到這個著名的數(shù)列:《練習(xí)》的練習(xí) 4,《函數(shù)(4)》中遞歸舉例):

#!/usr/bin/env Python
# coding=utf-8
"""
compute Fibonacci by iterator
"""
__metaclass__ = type

class Fibs:
    def __init__(self, max):
        self.max = max
        self.a = 0
        self.b = 1

    def __iter__(self):
        return self

    def next(self):
        fib = self.a
        if fib > self.max:
            raise StopIteration
        self.a, self.b = self.b, self.a + self.b
        return fib

if __name__ == "__main__":
    fibs = Fibs(5)
    print list(fibs)

運(yùn)行結(jié)果是:

$ python 21402.py 
[0, 1, 1, 2, 3, 5]

給讀者一個思考問題:要在斐波那契數(shù)列中找出大于 1000 的最小的數(shù),能不能在上述代碼基礎(chǔ)上改造得出呢?

關(guān)于列表和迭代器之間的區(qū)別,還有兩個非常典型的內(nèi)建函數(shù):range()xrange(),研究一下這兩個的差異,會有所收獲的。

range(...)
    range(stop) -> list of integers
    range(start, stop[, step]) -> list of integers

>>> dir(range)
['__call__', '__class__', '__cmp__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']

range() 的幫助文檔和方法中可以看出,它的結(jié)果是一個列表。但是,如果用 help(xrange) 查看:

class xrange(object)
 |  xrange(stop) -> xrange object
 |  xrange(start, stop[, step]) -> xrange object
 |  
 |  Like range(), but instead of returning a list, returns an object that
 |  generates the numbers in the range on demand.  For looping, this is 
 |  slightly faster than range() and more memory efficient.

xrange() 返回的是對象,并且進(jìn)一步告訴我們,類似 range(),但不是列表。在循環(huán)的時候,它跟 range() 相比“slightly faster than range() and more memory efficient”,稍快并更高的內(nèi)存效率(就是省內(nèi)存呀)。查看它的方法:

>>> dir(xrange)
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__getitem__', '__hash__', '__init__', '__iter__', '__len__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']

看到令人興奮的__iter__了嗎?說明它是可迭代的,它返回的是一個可迭代的對象。

也就是說,通過 range() 得到的列表,會一次性被讀入內(nèi)存,而 xrange() 返回的對象,則是需要一個數(shù)值才從返回一個數(shù)值。比如這樣一個應(yīng)用:

還記得 zip() 嗎?

>>> a = ["name", "age"]
>>> b = ["qiwsir", 40]
>>> zip(a,b)
[('name', 'qiwsir'), ('age', 40)]

如果兩個列表的個數(shù)不一樣,就會以短的為準(zhǔn)了,比如:

>>> zip(range(4), xrange(100000000))
[(0, 0), (1, 1), (2, 2), (3, 3)]

第一個 range(4) 產(chǎn)生的列表被讀入內(nèi)存;第二個是不是也太長了?但是不用擔(dān)心,它根本不會產(chǎn)生那么長的列表,因?yàn)橹恍枰?4 個數(shù)值,它就提供前四個數(shù)值。如果你要修改為 range(100000000),就要花費(fèi)時間了,可以嘗試一下哦。

迭代器的確有迷人之處,但是它也不是萬能之物。比如迭代器不能回退,只能如過河的卒子,不斷向前。另外,迭代器也不適合在多線程環(huán)境中對可變集合使用(這句話可能理解有困難,先混個臉熟吧,等你遇到多線程問題再說)。


總目錄   |   上節(jié):特殊方法(2)   |   下節(jié):生成器

如果你認(rèn)為有必要打賞我,請通過支付寶:qiwsir@126.com,不勝感激。

上一篇:標(biāo)準(zhǔn)庫 (2)下一篇:自省