迭代器、生成器、裝飾器
在這個實驗里我們學習迭代器、生成器、裝飾器有關知識。
知識點
- 迭代器
- 生成器
- 生成器表達式
- 閉包
- 裝飾器
實驗步驟
1. 迭代器
Python 迭代器(Iterators)對象在遵守迭代器協議時需要支持如下兩種方法。
__iter__()
,返回迭代器對象自身。這用在 for
和 in
語句中。
__next__()
,返回迭代器的下一個值。如果沒有下一個值可以返回,那么應該拋出 StopIteration
異常。
class Counter(object): def __init__(self, low, high): self.current = low self.high = high def __iter__(self): return self def __next__(self): '返回下一個值直到當前值大於 high' if self.current > self.high: raise StopIteration else: self.current += 1 return self.current - 1
現在我們能把這個迭代器用在我們的代碼里。
>>> c = Counter(5,10) >>> for i in c: ... print(i, end=' ') ... 5 6 7 8 9 10
請記住迭代器只能被使用一次。這意味着迭代器一旦拋出 StopIteration
,它會持續拋出相同的異常。
>>> c = Counter(5,6) >>> next(c) 5 >>> next(c) 6 >>> next(c) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 11, in next StopIteration >>> next(c) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 11, in next StopIteration
我們已經看過在 for
循環中使用迭代器的例子了,下面的例子試圖展示迭代器被隱藏的細節:
>>> iterator = iter(c) >>> while True: ... try: ... x = iterator.__next__() ... print(x, end=' ') ... except StopIteration as e: ... break ... 5 6 7 8 9 10
2. 生成器
在這一節我們學習有關 Python 生成器(Generators)的知識。生成器是更簡單的創建迭代器的方法,這通過在函數中使用 yield
關鍵字完成:
>>> def my_generator(): ... print("Inside my generator") ... yield 'a' ... yield 'b' ... yield 'c' ... >>> my_generator() <generator object my_generator at 0x7fbcfa0a6aa0>
在上面的例子中我們使用 yield
語句創建了一個簡單的生成器。我們能在 for
循環中使用它,就像我們使用任何其它迭代器一樣。
>>> for char in my_generator(): ... print(char) ... Inside my generator a b c
在下一個例子里,我們會使用一個生成器函數完成與 Counter 類相同的功能,並且把它用在 for 循環中。
>>> def counter_generator(low, high): ... while low <= high: ... yield low ... low += 1 ... >>> for i in counter_generator(5,10): ... print(i, end=' ') ... 5 6 7 8 9 10
在 While 循環中,每當執行到 yield
語句時,返回變量 low
的值並且生成器狀態轉為掛起。在下一次調用生成器時,生成器從之前凍結的地方恢復執行然后變量 low
的值增一。生成器繼續 while
循環並且再次來到 yield
語句...
當你調用生成器函數時它返回一個生成器對象。如果你把這個對象傳入 dir()
函數,你會在返回的結果中找到 __iter__
和 __next__
兩個方法名。
我們通常使用生成器進行惰性求值。這樣使用生成器是處理大數據的好方法。如果你不想在內存中加載所有數據,你可以使用生成器,一次只傳遞給你一部分數據。
os.path.walk()
函數是最典型的這樣的例子,它使用一個回調函數和當前的 os.walk
生成器。使用生成器實現節約內存。
我們可以使用生成器產生無限多的值。以下是一個這樣的例子。
>>> def infinite_generator(start=0): ... while True: ... yield start ... start += 1 ... >>> for num in infinite_generator(4): ... print(num, end=' ') ... if num > 20: ... break ... 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
如果我們回到 my_generator()
這個例子,我們會發現生成器的一個特點:它們是不可重復使用的。
>>> g = my_generator() >>> for c in g: ... print(c) ... Inside my generator a b c >>> for c in g: ... print(c) ...
一個創建可重復使用生成器的方式是不保存任何狀態的基於對象的生成器。任何一個生成數據的含有 __iter__
方法的類都可以用作對象生成器。在下面的例子中我們重新創建了 counter
生成器。
>>> class Counter(object): ... def __init__(self, low, high): ... self.low = low ... self.high = high ... def __iter__(self): ... counter = self.low ... while self.high >= counter: ... yield counter ... counter += 1 ... >>> gobj = Counter(5, 10) >>> for num in gobj: ... print(num, end=' ') ... 5 6 7 8 9 10 >>> for num in gobj: ... print(num, end=' ') ... 5 6 7 8 9 10
3. 生成器表達式
在這一節我們學習生成器表達式(Generator expressions),生成器表達式是列表推導式和生成器的一個高性能,內存使用效率高的推廣。
舉個例子,我們嘗試對 1 到 9 的所有數字進行平方求和。
>>> sum([x*x for x in range(1,10)])
這個例子實際上首先在內存中創建了一個平方數值的列表,然后遍歷這個列表,最終求和后釋放內存。你能理解一個大列表的內存占用情況是怎樣的。
我們可以通過使用生成器表達式來節省內存使用。
>>> sum(x*x for x in range(1,10))
生成器表達式的語法要求其總是直接在在一對括號內,並且不能在兩邊有逗號。這基本上意味着下面這些例子都是有效的生成器表達式用法示例:
>>> sum(x*x for x in range(1,10)) 285 >>> g = (x*x for x in range(1,10)) >>> g <generator object <genexpr> at 0x7fc559516b90>
我們可以把生成器和生成器表達式聯系起來,在下面的例子中我們會讀取文件 '/var/log/cron'
並且查看任意指定任務(例中我們搜索 'anacron'
)是否成功運行。
我們可以用 shell 命令 tail -f /etc/crontab |grep anacron
完成同樣的事(按 Ctrl + C 終止命令執行)。
>>> jobtext = 'anacron' >>> all = (line for line in open('/etc/crontab', 'r') ) >>> job = ( line for line in all if line.find(jobtext) != -1) >>> text = next(job) >>> text '25 6\t* * *\troot\ttest -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )\n' >>> text = next(job) >>> text '47 6\t* * 7\troot\ttest -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )\n' >>> text = next(job) >>> text '52 6\t1 * *\troot\ttest -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )\n'
你可以寫一個 for
循環遍歷所有行。
4. 閉包
閉包(Closures)是由另外一個函數返回的函數。我們使用閉包去除重復代碼。在下面的例子中我們創建了一個簡單的閉包來對數字求和。
>>> def add_number(num): ... def adder(number): ... 'adder 是一個閉包' ... return num + number ... return adder ... >>> a_10 = add_number(10) >>> a_10(21) 31 >>> a_10(34) 44 >>> a_5 = add_number(5) >>> a_5(3) 8
adder
是一個閉包,把一個給定的數字與預定義的一個數字相加。
5. 裝飾器
裝飾器(Decorators)用來給一些對象動態的添加一些新的行為,我們使用過的閉包也是這樣的。
我們會創建一個簡單的示例,將在函數執行前后打印一些語句。
>>> def my_decorator(func): ... def wrapper(*args, **kwargs): ... print("Before call") ... result = func(*args, **kwargs) ... print("After call") ... return result ... return wrapper ... >>> @my_decorator ... def add(a, b): ... "我們的求和函數" ... return a + b ... >>> add(1, 3) Before call After call 4
總結
本實驗我們學習了迭代器和生成器以及裝飾器這幾個高級特性的定義方法和用法,也了解了怎樣使用生成器表達式和怎樣定義閉包。