Python 迭代器、生成器和列表解析

迭代器

迭代器在 Python 2.2 版本中被加入, 它爲類序列對象提供了一個類序列的接口。 Python 的迭代無縫地支持序列對象, 並且它還容許迭代非序列類型, 包括用戶定義的對象。即迭代器能夠迭代不是序列但表現出序列行爲的對象, 例如字典的 key , 一個文件的行, 等等。迭代器有如下特性:python

  • 提供了可擴展的迭代器接口.
  • 對列表迭代帶來了性能上的加強.
  • 在字典迭代中性能提高.
  • 建立真正的迭代接口, 而不是原來的隨機對象訪問.
  • 與全部已經存在的用戶定義的類以及擴展的模擬序列和映射的對象向後兼容
  • 迭代非序列集合(例如映射和文件)時, 能夠建立更簡潔可讀的代碼.

迭代器對象即實現了迭代器協議的對象,在 Python 中,支持迭代器協議就是實現對象的 __iter__()next() 方法(注:在 Python3 中被改成 next 方法)。其中 __iter__() 方法返回迭代器對象自己;next() 方法返回容器的下一個元素,在結尾時引起 StopIteration 異常。編程

迭代器協議

迭代器協議即實現 __iter__()next() 方法。這兩個方法是迭代器最基本的方法,一個用來得到迭代器對象,一個用來獲取容器中的下一個元素。對於可迭代對象,可使用內建函數 iter() 來獲取它的迭代器對象:app

li = [1, 2]
it = iter(li)
print it

print it.next()
print it.next()
print it.next()

結果以下所示:編程語言

<listiterator object at 0xb708aa6c>
1
2
Traceback (most recent call last):
  File "iter.py", line 21, in <module>
    print it.next()
StopIteration

列表(list)自己是可迭代的,經過 iter() 方法能夠得到其迭代器對象,而後就能夠經過 next() 方法來訪問 list 中的元素。當容器中沒有能夠訪問的元素時, next() 方法將會拋出一個 StopIteration 的異常,從而終止迭代器。當咱們使用 for 語句的時候,for 語句就會自動的經過 __iter__() 方法來得到迭代器對象,而且經過 next() 方法來獲取下一個元素,遇到 StopIteration 異常時會自動結束迭代。函數式編程

自定義迭代器

本身建立迭代器實際上就是實現一個帶有 __iter__() 方法和 next() 方法的類,用該類建立的實例便是可迭代對象。例如咱們用迭代器來實現斐波那契數列:函數

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

    def next(self):
        self.a, self.b = self.b, self.a + self.b
        return self.a

    def __iter__(self):
        return self

fibs = Fibs()  // 這將獲得一個無窮的數列
for f in fibs:
    if f > 1000:
        print f
        break
    else:
        print f

這裏有一個問題,大多數的序列或者類序列都不是無窮的,因此在達到必定條件後就該終止。所以咱們須要在序列或者類序列須要結束時引起 StopIteration 異常:工具

class MyRange(object):
    def __init__(self, n):
        self.idx = 0
        self.n = n

    def __iter__(self):
        return self

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

myRange = MyRange(3)
for i in myRange:
        print i

可迭代對象和迭代器對象

可迭代對象即具備 __iter__() 方法的對象,該方法可獲取其迭代器對象。迭代器對象即具備 next() 方法的對象。也就是說,一個實現了 __iter_() 的對象是可迭代的,一個實現了 next() 方法的對象則是迭代器。可迭代對象也能夠是迭代器對象,如文件對象。此時可迭代對象本身有 next() 方法,而其 __iter() 方法返回的就是它本身。對於許多內置對象及其派生對象,如 list、dict 等,因爲須要支持屢次打開迭代器,所以本身並不是迭代器對象,須要用 __iter_() 方法返回其迭代器對象,並用迭代器對象來訪問其它元素。性能

以上例子中的 myRange 這個對象就是一個可迭代對象,同時它自己也是一個迭代器對象。對於一個可迭代對象,若是它自己又是一個迭代器對象,就會有這樣一個問題,其沒有辦法支持屢次迭代。以下所示:lua

myRange = MyRange(3)
print myRange is iter(myRange)

print [i for i in myRange]
print [i for i in myRange]

運行結果:3d

True
[0, 1, 2]
[]

爲了解決上面的問題,能夠分別定義可迭代類型對象和迭代器類型對象;而後可迭代類型對象的 __iter__() 方法能夠得到一個迭代器類型的對象。以下所示:

class Zrange:
    def __init__(self, n):
        self.n = n

    def __iter__(self):
        return ZrangeIterator(self.n)

class ZrangeIterator:
    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()    

zrange = Zrange(3)
print zrange is iter(zrange)         

print [i for i in zrange]
print [i for i in zrange]

運行結果:

False
[0, 1, 2]
[0, 1, 2]

另外, reversed() 內建函數將返回一個反序訪問的迭代器,enumerate() 內建函數一樣也返回迭代器。例如能夠用 enumerate() 函數遍歷列表:

ll = [1, 2, 3]
print enumerate(ll)

/* 優雅的遍歷列表 */
for i, ele in enumerate(ll):
    print i, ll[i]

生成器

迭代器和生成器多是近幾年引入的最強大的兩個特性。生成器是一種用普通的函數語法定義的迭代器,也就是說生成器實際上就是一個函數。可是生成器不用 return 返回,而是用 yield 一次返回一個結果,在每一個結果之間掛起和繼續它們的狀態,來自動實現迭代協議。任何包含 yield 語句的函數稱爲生成器。yield 被人們優雅的稱之爲語法糖,意思就是包在裏邊的甜心。在 yield 的內部是一個狀態機,維護着掛起和繼續的狀態。

生成器執行流程

先看看下邊的列子:

def Zrange(n):
    print "beginning of Zrange"
    i = 0
    while i < n:
        print "before yield", i
        yield i
        i += 1
        print "after yield", i

    print "endding of Zrange"

zrange = Zrange(3)
print "------------"

print zrange.next()
print "------------"

print zrange.next()
print "------------"

print zrange.next()
print "------------"

print zrange.next()def flatten(nested):
    result = []
    try:
        # 不要迭代相似字符串的對象
        try: nested + ""
        except TypeError: pass
        else: raise TypeError
        for sublist in nested:
            for element in flatten(sublist):
                result.append(element)
    except TypeError:
        result.append(nested)
    return result
print "------------"

執行結果:

--------------------
beginning of Zrange
before yield 0
0
--------------------
after yield 1
before yield 1
1
--------------------
after yield 2
before yield 2
2
--------------------
after yield 3
endding of Zrange
Traceback (most recent call last):
  File "one.py", line 38, in &lt;module>
    print zrange.next()
StopIteration

經過結果能夠看到:

  • 當調用生成器函數的時候,函數只是返回了一個生成器對象,並無 執行。
  • 當next()方法第一次被調用的時候,生成器函數纔開始執行,執行到yield語句處中止
  • next()方法的返回值就是yield語句處的參數(yielded value)
  • 當繼續調用next()方法的時候,函數將接着上一次中止的yield語句處繼續執行,併到下一個yield處中止;若是後面沒有yield就拋出StopIteration異常

遞歸生成器

生成器能夠向函數同樣進行遞歸使用,下面列舉兩個示例:

對一個序列進行全排列:

def permutations(li):
    if len(li) == 0:
        yield li
    else:
        for i in range(len(li)):
            li[0], li[i] = li[i], li[0] #
            for item in permutations(li[1:]):
                yield [li[0]] + item

for item in permutations(range(3)):
    print item

這裏的實現思路是,每次取序列中不同的元素放在最前面,直到只有一個元素時返回,並將返回結果日後拼接。

展開多層嵌套的列表:

def flatten(nested):
    try:
        # 不要迭代相似於字符串的對象
        try: nested + ""
        except TypeError: pass
        else: raise TypeError
        for sublist in nested:
            for element in flatten(sublist):
                yield element
    except TypeError:
        yield nested

print list(flatten([[[1], 2], 3, 4, [5, [6, 7]], 8]))

這裏須要注意的是,不該該在 flatten 函數中對相似於字符串的對象進行迭代,這樣會致使無窮遞歸,由於一個字符串的第一個元素是另外一個長度爲1的字符串,而長度爲一個字符串的第一個元素就是字符串自己。

通用生成器

生成器能夠人爲是由兩部分組成:生成器的函數和生成器的迭代器。生成器的函數是用 def 語句定義的,包含 yield 部分,生成器的迭代器是這個函數返回的部分。按照一種不是很準確的說法,兩個實體常常被當作一個,合起來叫作生成器。以下實例所示:

def simple_generator():
    yield 1

print simple_generator
print simple_generator()

運行結果:

<function simple_generator at 0xb743d79c>
<generator object simple_generator at 0xb71c7be4>

生成器方法

  • send(value)

外部做用域訪問生成器的 send 方法,就像訪問 next() 方法同樣。next()方法能夠恢復生成器狀態並繼續執行,其實 send() 是除 next() 外另外一個恢復生成器的方法。Python 2.5 中,yield 語句變成了 yield 表達式,也就是說 yield 能夠有一個值,而這個值就是send()方法的參數,因此 send(None) 和 next() 是等效的。一樣,next()和send()的返回值都是 yield語 句處的參數(yielded value)。使用 send() 方法只有在生成器掛起以後纔有意義,若是真想對剛剛啓動的生成器使用 send 方法,則能夠將 None 做爲參數進行調用。也就是說, 第一次調用時,要使用 next() 語句或 send(None),由於沒有 yield 語句來接收這個值

  • throw()

用於在生成器內引起一個異常。

  • close()

用於中止生成器,調用它時,會在 yield 運行出引起一個 GeneratorExit 異常。

使用示例:

def Zrange(n):
    i = 0
    while i < n:
        val = yield i
        print "val is", val
        i += 1

zrange = Zrange(5)

print zrange.next()
print zrange.next()
print zrange.send("hello")
print zrange.next()
#print zrange.next()

zrange.close()

print zrange.send("world")

模擬生成器

在舊的 Python 版本中並不支持生成器,那麼咱們能夠用普通的函數來模擬生成器。以下所示:

def flatten(nested):
    result = []
    try:
        # 不要迭代相似字符串的對象
        try: nested + ""
        except TypeError: pass
        else: raise TypeError
        for sublist in nested:
            for element in flatten(sublist):
                result.append(element)
    except TypeError:
        result.append(nested)
    return result

儘管這個版本可能不適用於全部的生成器,但對大多數生成器來講是可行的。好比,它不適用於一個無限的生成器。

列表解析和生成器表達式

列表解析

列表解析( List comprehensions, 或縮略爲 list comps ) 來自函數式編程語言 Haskell . 它是一個很是有用, 簡單, 並且靈活的工具, 能夠用來動態地建立列表。其語法結構爲:

[expr for iter_var in iterable]

這個語句的核心是 for 循環, 它迭代 iterable 對象的全部條目. 前邊的 expr 應用於序列的每一個成員, 最後的結果值是該表達式產生的列表。 迭代變量並不須要是表達式的一部分。例如用 lambda 函數計算序列成員的平方的表達是爲:

map(lambda x: x ** 2, range(6))

這能夠用列表解析來改寫:

[x ** 2 for x in range(6)]

列表解析的表達式能夠取代內建的 map() 函數以及 lambda , 並且效率更高。結合 if 語句,列表解析還提供了一個擴展版本的語法:

[expr for iter_var in iterable if cond_expr]

這個語法在迭代時會過濾/捕獲知足條件表達式 cond_expr 的序列成員。例如挑選出序列中的奇數能夠用下邊的方法:

[x for x in seq if x % 2]

列表解析還有不少巧妙的應用:

迭代一個有三行五列的矩陣:

[(x+1,y+1) for x in range(3) for y in range(5)]

計算出全部非空白字符的數目:

f = open('hhga.txt', 'r')

len([word for line in f for word in line.split()])

生成器表達式

生成器表達式是列表解析的一個擴展。列表解析的一個不足就是必要生成全部的數據, 用以建立整個列表。這可能對有大量數據的迭代器有負面效應。生成器表達式經過結合列表解析和生成器解決了這個問題。生成器表達式在 Python 2.4 被引入, 它與列表解析很是類似,並且它們的基本語法基本相同; 不過它並不真正建立數字列表, 而是返回一個生成器,這個生成器在每次計算出一個條目後,把這個條目「產生」(yield)出來。生成器表達式使用了"延遲計算"(lazy evaluation), 因此它在使用內存上更有效。生成器表達式語法:

(expr for iter_var in iterable if cond_expr)

生成器並不會讓列表解析廢棄, 它只是一個內存使用更友好的結構, 基於此, 有不少使用生 成器地方,以下所示:

快速地計算文件大小:

上面咱們用列表解析計算出了文件中非空白字符的數目,那麼只要用 sum() 函數對每一個單詞的長度求和,則可大體計算出文件的大小。sum() 函數的參數不只能夠是列表,還能夠是可迭代對象,好比生成器表達式。這裏咱們用生成器表達式改寫整個過程:

sum(len(word) for line in data for word in line.split())

交叉配對:

生成器表達式就好像是懶惰的列表解析(這反而成了它主要的優點)。它還能夠用來處理其餘列表或生成器:

rows = [1, 2, 3, 17]

def cols(): # example of simple generator
    yield 56  
    yield 2  
    yield 1

x_product_pairs = ((i, j) for i in rows for j in cols())

for pair in x_product_pairs:
    print pair

尋找文件最長的行:

def longest(filename):
    glines = (x.strip() for x in open(filename))
    return max([len(l) for l in glines])

# Script starts from here

if __name__ == "__main__":
    print longest("/etc/hosts")

這個例子摘自 《Python核心編程》 中生成器表達式一節,做者在原書中只用了一行代碼來實現這個功能,即:

return max(len(x.strip()) for x in open('/etc/motd'))

這行代碼會報出以下錯誤:

TypeError: object of type 'generator' has no len()

也就是說生成器沒有 len() 方法,因此這樣並不可行,可是用列表解析則能夠用一行實現:

return max([len(x.strip()) for x in open("/etc/motd")])
相關文章
相關標籤/搜索