# 2.1 Python高級功能(Constructs)
**作者**: Zbigniew J?drzejewski-Szmek
這一章是關于Python語言的高級特性-從不是每種語言都有這些特性的角度來說,也可以從他們在更復雜的程序和庫中更有用這個角度來說,但是,并不是說特別專業或特別復雜。
需要強調的是本章是純粹關于語言本身-關于由特殊語法支持的特性,用于補充Python標準庫的功能,聰明的外部模塊不會實現這部分特性。
開發Python程序語言的流程、語法是惟一的因為非常透明,提議的修改會在公共郵件列表中從多種角度去評估,最終的決策是來自于想象中的用例的重要性、帶來更多語言特性所產生的負擔、與其他語法的一致性及提議的變化是否易于讀寫和理解的權衡。這個流程被定型在Python增強建議中-[PEPs](http://www.python.org/dev/peps/)。因此,本章中的特性都是在顯示出確實解決了現實問題,并且他們的使用盡可能簡潔后才被添加的。
## 2.1.1 迭代器、生成器表達式和生成器
### 2.1.1.1 迭代器
簡潔
重復的工作是浪費,用一個標準的特性替代不同的自產方法通常使事物的可讀性和共用性更好。 Guido van Rossum — [為Python添加可選的靜態輸入](http://www.artima.com/weblogs/viewpost.jsp?thread=86641)
迭代器是繼承了[迭代協議](http://docs.python.org/dev/library/stdtypes.html#iterator-types)的對象-本質上,這意味著它有一個[next](http://docs.python.org/2.7/library/stdtypes.html#iterator.next)方法,調用時會返回序列中的下一個項目,當沒有東西可返回時,拋出[StopIteration](http://docs.python.org/2.7/library/exceptions.html#exceptions.StopIteration)異常。
迭代器對象只允許循環一次。它保留了單次迭代中的狀態(位置),或者從另外的角度來看,序列上的每次循環需要一個迭代器對象。這意味著我們可以在一個序列上同時循環多次。從序列上分離出循環邏輯使我們可以有不止一種方法去循環。
在容器上調用[__iter__](http://docs.python.org/2.7/reference/datamodel.html#object.__iter__)方法來創建一個迭代器對象是獲得迭代器的最簡單方式。[iter](http://docs.python.org/2.7/library/functions.html#iter)函數幫我們完成這項工作,節省了一些按鍵次數。
In?[12]:
```
nums = [1,2,3] # 注意 ... 變化: 這些是不同的對象
iter(nums)
```
Out[12]:
```
<listiterator at 0x105f8b490>
```
In?[2]:
```
nums.__iter__()
```
Out[2]:
```
<listiterator at 0x105bd9bd0>
```
In?[3]:
```
nums.__reversed__()
```
Out[3]:
```
<listreverseiterator at 0x105bd9c50>
```
In?[4]:
```
it = iter(nums)
next(it) # next(obj)是obj.next()的簡便用法
```
Out[4]:
```
1
```
In?[5]:
```
it.next()
```
Out[5]:
```
2
```
In?[6]:
```
next(it)
```
Out[6]:
```
3
```
In?[7]:
```
next(it)
```
```
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-7-2cdb14c0d4d6> in <module>()
----> 1 next(it)
StopIteration:
```
在一個循環上使用時,[StopIteration](http://docs.python.org/2.7/library/exceptions.html#exceptions.StopIteration) 被忍受了,使循環終止。但是,當顯式調用時,我們可以看到一旦迭代器結束,再訪問它會拋出異常。
使用[for..in](http://docs.python.org/2.7/reference/compound_stmts.html#for) 循環也使用`__iter__`方法。這個方法允許我們在序列上顯式的開始一個循環。但是,如果我們已經有個迭代器,我們想要可以在一個循環中以同樣的方式使用它。要做到這一點,迭代器及`next`也需要有一個稱為`__iter__`的方法返回迭代器(`self`)。
Python中對迭代器的支持是普遍的:標準庫中的所有的序列和無序容器都支持迭代器。這個概念也被擴展到其他的事情:例如文件對象支持按行循環。
In?[10]:
```
f = open('./etc/fstab')
f is f.__iter__()
```
Out[10]:
```
True
```
`文件`是迭代器本身,它的`__iter__`方法并不創建一個新的對象:僅允許一個單一線程的序列訪問。
### 2.1.1.2 生成器表達式
創建迭代器對象的第二種方式是通過生成器表達式,這也是列表推導的基礎。要增加明確性,生成器表達式通常必須被括號或表達式包圍。如果使用圓括號,那么創建了一個生成器迭代器。如果使用方括號,那么過程被縮短了,我們得到了一個列表。
In?[13]:
```
(i for i in nums)
```
Out[13]:
```
<generator object <genexpr> at 0x105fbc320>
```
In?[14]:
```
[i for i in nums]
```
Out[14]:
```
[1, 2, 3]
```
In?[15]:
```
list(i for i in nums)
```
Out[15]:
```
[1, 2, 3]
```
在Python 2.7和3.x中,列表推導語法被擴展為**字典和集合推導**。當生成器表達式被大括號包圍時創建一個`集合`。當生成器表達式包含一對`鍵:值`的形式時創建`字典`:
In?[16]:
```
{i for i in range(3)}
```
Out[16]:
```
{0, 1, 2}
```
In?[17]:
```
{i:i**2 for i in range(3)}
```
Out[17]:
```
{0: 0, 1: 1, 2: 4}
```
如果你還在前面一些Python版本,那么語法只是有一點不同:
In?[18]:
```
set(i for i in 'abc')
```
Out[18]:
```
{'a', 'b', 'c'}
```
In?[19]:
```
dict((i, ord(i)) for i in 'abc')
```
Out[19]:
```
{'a': 97, 'b': 98, 'c': 99}
```
生成器表達式非常簡單,在這里沒有什么多說的。只有一個疑難問題需要提及:在舊的Python中,索引變量(i)可以泄漏,在>=3以上的版本,這個問題被修正了。
### 2.1.1.3 生成器
生成器
生成器是一個可以產生一個結果序列而不是單一值的函數。
David Beazley — [協程和并發的有趣課程](http://www.dabeaz.com/coroutines/)
創建迭代器對應的第三種方法是調用生成器函數。**生成器**是包含關鍵字[yield](http://docs.python.org/2.7/reference/simple_stmts.html#yield)的函數。必須注意,只要這個關鍵詞出現就會徹底改變函數的本質:這個`yield`關鍵字并不是必須激活或者甚至可到達,但是,會造成這個函數被標記為一個生成器。當普通函數被調用時,函數體內包含的指令就開始執行。當一個生成器被調用時,在函數體的第一條命令前停止執行。調用那個生成器函數創建一個生成器對象,繼承迭代器協議。與調用普通函數一樣,生成器也允許并發和遞歸。
當`next`被調用時,函數執行到第一個`yield`。每一次遇到`yield`語句都會給出`next`的一個返回值。執行完yield語句,就暫停函數的執行。
In?[20]:
```
def f():
yield 1
yield 2
f()
```
Out[20]:
```
<generator object f at 0x105fbc460>
```
In?[21]:
```
gen = f()
gen.next()
```
Out[21]:
```
1
```
In?[22]:
```
gen.next()
```
Out[22]:
```
2
```
In?[23]:
```
gen.next()
```
```
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-23-b2c61ce5e131> in <module>()
----> 1 gen.next()
StopIteration:
```
讓我們進入一次調用生成器函數的生命周期。
In?[24]:
```
def f():
print("-- start --")
yield 3
print("-- middle --")
yield 4
print("-- finished --")
gen = f()
next(gen)
```
```
-- start --
```
Out[24]:
```
3
```
In?[25]:
```
next(gen)
```
```
-- middle --
```
Out[25]:
```
4
```
In?[26]:
```
next(gen)
```
```
-- finished --
```
```
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-26-67c2d9ac4268> in <module>()
----> 1 next(gen)
StopIteration:
```
與普通函數不同,當執行`f()`時會立即執行第一個`print`,函數賦值到`gen`沒有執行函數體內的任何語句。只有當用`next`激活`gen.next()`時,截至到第一個yield的語句才會被執行。第二個`next`打印`-- middle --`,執行到第二個`yield`終止。 第三個`next`打印`-- finished --`,并且到達了函數末尾。因為沒有找到`yield`,拋出異常。
當向調用者傳遞控制時,在yield之后函數內發生了什么?每一個生成器的狀態被存儲在生成器對象中。從生成器函數的角度,看起來幾乎是在一個獨立的線程運行,但是,這是一個假象:執行是非常嚴格的單線程,但是解釋器記錄并恢復`next`值請求間的狀態。
為什么生成器有用?正如迭代器部分的提到的,生成器只是創建迭代對象的不同方式。用`yield`語句可以完成的所有事,也都可以用`next`方法完成。盡管如此,使用函數,并讓解釋器執行它的魔法來創建迭代器有優勢。函數比定義一個帶有`next`和`__iter__`方法的類短很多。更重要的是,理解在本地變量中的狀態比理解實例屬性的狀態對于生成器的作者來說要簡單的多,對于后者來事必須要在迭代對象上不斷調用`next`。
更廣泛的問題是為什么迭代器有用?當迭代器被用于循環時,循環變的非常簡單。初始化狀態、決定循環是否結束以及尋找下一個值的代碼被抽取到一個獨立的的地方。這強調了循環體 - 有趣的部分。另外,這使在其他地方重用這些迭代體成為可能。
### 2.1.1.4 雙向溝通
每個`yield`語句將一個值傳遞給調用者。這是由[PEP 255](http://www.python.org/dev/peps/pep-0255)(在Python2.2中實現)引入生成器簡介的原因。但是,相反方向的溝通也是有用的。一個明顯的方式可以是一些外部狀態,全局變量或者是共享的可變對象。感謝[PEP 342](http://www.python.org/dev/peps/pep-0342)(在2.5中實現)使直接溝通成為可能。它通過將之前枯燥的`yeild`語句轉換為表達式來實現。當生成器在一個`yeild`語句后恢復執行,調用者可以在生成器對象上調用一個方法,或者向生成器內部傳遞一個值,稍后由`yield`語句返回,或者一個不同的方法向生成器注入一個異常。
第一個新方法是[send(value)](http://docs.python.org/2.7/reference/expressions.html#generator.send),與[next()](http://docs.python.org/2.7/reference/expressions.html#generator.next)類似,但是,向生成器傳遞值用于`yield`表達式來使用。實際上,`g.next()`和`g.send(None)`是等價的。
第二個新方法是[throw(type, value=None, traceback=None)](http://docs.python.org/2.7/reference/expressions.html#generator.throw)等價于:
In?[?]:
```
raise type, value, traceback
```
在`yield`語句的點上。
與[raise](https://docs.python.org/2.7/reference/simple_stmts.html#raise)不同 (在當前執行的點立即拋出異常), `throw()`只是首先暫停生成器,然后拋出異常。挑選throw這個詞是因為它讓人聯想到將異常放在不同的位置,這與其他語言中的異常相似。
當異常在生成器內部拋出時發生了什么?它可以是顯性拋出或者當執行一些語句時,或者它可以注入在`yield`語句的點上,通過`throw()`方法的意思。在任何情況下,這些異常用一種標準方式傳播:它可以被`except`或`finally`語句監聽,或者在其他情況下,它引起生成器函數的執行中止,并且傳播給調用者。
為了完整起見,應該提一下生成器迭代器也有[close()](http://docs.python.org/2.7/reference/expressions.html#generator.close)函數,可以用來強制一個可能在其他情況下提供更多的值的生成器立即結束。它允許生成器[**del**](http://docs.python.org/2.7/reference/datamodel.html#object.__del__)函數去銷毀保持生成器狀態的對象。
讓我們定義一個生成器,打印通過send和throw傳遞的內容。
In?[2]:
```
import itertools
def g():
print '--start--'
for i in itertools.count():
print '--yielding %i--' % i
try:
ans = yield i
except GeneratorExit:
print '--closing--'
raise
except Exception as e:
print '--yield raised %r--' % e
else:
print '--yield returned %s--' % ans
```
In?[3]:
```
it = g()
next(it)
```
```
--start--
--yielding 0--
```
Out[3]:
```
0
```
In?[4]:
```
it.send(11)
```
```
--yield returned 11--
--yielding 1--
```
Out[4]:
```
1
```
In?[5]:
```
it.throw(IndexError)
```
```
--yield raised IndexError()--
--yielding 2--
```
Out[5]:
```
2
```
In?[6]:
```
it.close()
```
```
--closing--
```
**next 還是 __next__?**
在Python2.X中,迭代器用于取回下一個值的方法是調用[next](http://docs.python.org/2.7/library/stdtypes.html#iterator.next)。它通過全局方法[next](http://docs.python.org/2.7/library/stdtypes.html#iterator.next)來喚醒,這意味著它應該調用__next__。就像全局函數[iter](http://docs.python.org/2.7/library/functions.html#iter)調用__iter__。在Python 3.X中修正了這種前后矛盾,it.next變成it.__next__。對于其他的生成器方法 - `send`和`throw`-情況更加復雜,因為解釋器并不隱性的調用它們。盡管如此,人們提出一種語法擴展,以便允許`continue`接收一個參數,用于傳遞給循環的迭代器的[send](http://docs.python.org/2.7/reference/expressions.html#generator.send)。如果這個語法擴展被接受,那么可能`gen.send`將變成`gen.__send__`。最后一個生成器函數,[close](http://docs.python.org/2.7/reference/expressions.html#generator.close)非常明顯是命名錯誤,因為,它已經隱性被喚起。
### 2.1.1.5 生成器鏈
**注**:這是[PEP 380](http://www.python.org/dev/peps/pep-0380)的預覽(沒有實現,但是已經被Python3.3接受)。
假設我們正在寫一個生成器,并且我們想要量產(yield)由第二個生成器生成的一堆值,**子生成器**。如果只關心量產值,那么就可以沒任何難度的用循環實現,比如
In?[?]:
```
for v in subgen:
yield v
```
但是,如果子生成器想要與調用者通過`send()`、`throw()`和`close()`正確交互,事情就會變得復雜起來。`yield`語句必須用[try..except..finally](http://docs.python.org/2.7/reference/compound_stmts.html#try)結構保護起來,與前面的生成器函數“degug”部分定義的類似。在[PEP 380](http://www.python.org/dev/peps/pep-0380#id13)提供了這些代碼,現在可以說在Python 3.3中引入的新語法可以適當的從子生成器量產:
In?[?]:
```
yield from some_other_generator()
```
這個行為與上面的顯性循環類似,重復從`some_other_generator`量產值直到生成器最后,但是,也可以向前對子生成器`send`、`throw`和`close`。
## 2.1.2 修飾器
概述
這個令人驚訝功能在這門語言中出現幾乎是有歉意的,并且擔心它是否真的那么有用。
Bruce Eckel — Python修飾器簡介
因為函數或類是對象,因此他們都可以傳遞。因為可以是可變的對象,所以他們可以被修改。函數或類對象被構建后,但是在綁定到他們的名稱之前的修改行為被稱為修飾。
在“修飾器”這個名稱后面隱藏了兩件事-一件是進行修飾工作(即進行真實的工作)的函數,另一件是遵守修飾器語法的表達式,[[email?protected]](/cdn-cgi/l/email-protection)
用函數的修飾器語法可以修飾函數:
In?[?]:
```
@decorator # ②
def function(): # ①
pass
```
* 用標準形式定義的函數。①
* [[email?protected]](/cdn-cgi/l/email-protection)??[[email?protected]](/cdn-cgi/l/email-protection),通常,這只是函數或類的名字。這部分首先被評估,在下面的函數定義完成后,修飾器被調用,同時將新定義的函數對象作為惟一的參數。修飾器的返回值被附加到函數的原始名稱上。
修飾器可以被應用于函數和類。對于類,語法是一樣的 - 原始類定義被作為一個參數來調用修飾器,并且無論返回什么都被賦給原始的名稱。在修飾器語法實現之前([PEP 318](http://www.python.org/dev/peps/pep-0318)),通過將函數或類對象賦給一個臨時的變量,然后顯性引用修飾器,然后將返回值賦給函數的名稱,也可以到達相同的效果。這聽起來像是打更多的字,確實是這樣,并且被修飾函數的名字也被打了兩次,因為臨時變量必須被使用至少三次,這很容易出錯。無論如何,上面的例子等同于:
In?[?]:
```
def function(): # ①
pass
function = decorator(function) # ②
```
修飾器可以嵌套 - 應用的順序是由底到頂或者由內到外。含義是最初定義的函數被第一個修飾器作為參數使用,第一個修飾器返回的內容被用于第二個修飾器的參數,...,最后一個修飾器返回的內容被綁定在最初的函數名稱下。
選擇這種修飾器語法是因為它的可讀性。因為是在函數頭之前指定的,很明顯它并不是函數體的一部分,并且很顯然它只能在整個函數上運行。因為,[[email?protected]](/cdn-cgi/l/email-protection)("在你臉上",按照PEP的說法 :))。當使用多個修飾器時,每一個都是單獨的一行,一種很容易閱讀的方式。
### 2.1.2.1 替換或調整原始對象
修飾器可以返回相同的函數或類對象,也可以返回完全不同的對象。在第一種情況下,修飾器可以利用函數和類對象是可變的這個事實,并且添加屬性,即為類添加修飾字符串。修飾器可以做一些有用的事甚至都不需要修改對象,例如,在全局登記中登記被修飾的類。在第二種情況下,虛擬任何東西都是可能的:當原始函數或類的一些東西被替換了,那么新對象就可以是完全不同的。盡管如此,這種行為不是修飾器的目的:他們的目的是微調被修飾的對象,而不是一些不可預測的東西。因此,當一個”被修飾的“函數被用一個不同的函數替換,新函數通常調用原始的函數,在做完一些預備工作之后。同樣的,當”被修飾的“類被新的類替換,新類通常也來自原始類。讓修飾器的目的是”每次“都做一些事情,比如在修飾器函數中登記每次調用,只能使用第二類修飾器。反過來,如果第一類就足夠了,那么最好使用第一類,因為,它更簡單。
### 2.1.2.2 像類和函數一樣實現修飾器
修飾器的惟一一個要求是可以用一個參數調用。這意味著修飾器可以像一般函數一樣實現,或者像類用__call__方法實現,或者在理論上,甚至是lambda函數。讓我們比較一下函數和類的方法。修飾器表達式(@后面的部分)可以僅僅是一個名字,或者一次調用。僅使用名字的方式很好(輸入少,看起來更整潔等),但是,只能在不需要參數來自定義修飾器時使用。作為函數的修飾器可以用于下列兩個情況:
In?[1]:
```
def simple_decorator(function):
print "doing decoration"
return function
@simple_decorator
def function():
print "inside function"
```
```
doing decoration
```
In?[2]:
```
function()
```
```
inside function
```
In?[6]:
```
def decorator_with_arguments(arg):
print "defining the decorator"
def _decorator(function):
# in this inner function, arg is available too
print "doing decoration,", arg
return function
return _decorator
@decorator_with_arguments("abc")
def function():
print "inside function"
```
```
defining the decorator
doing decoration, abc
```
上面兩個修飾器屬于返回原始函數的修飾器。如果他們返回一個新的函數,則需要更多一層的嵌套。在最壞的情況下,三層嵌套的函數。
In?[7]:
```
def replacing_decorator_with_args(arg):
print "defining the decorator"
def _decorator(function):
# in this inner function, arg is available too
print "doing decoration,", arg
def _wrapper(*args, **kwargs):
print "inside wrapper,", args, kwargs
return function(*args, **kwargs)
return _wrapper
return _decorator
@replacing_decorator_with_args("abc")
def function(*args, **kwargs):
print "inside function,", args, kwargs
return 14
```
```
defining the decorator
doing decoration, abc
```
In?[8]:
```
function(11, 12)
```
```
inside wrapper, (11, 12) {}
inside function, (11, 12) {}
```
Out[8]:
```
14
```
定義`_wrapper`函數來接收所有位置和關鍵詞參數。通常,我們并不知道被修飾的函數可能接收什么參數,因此封裝器函數只是向被封裝的函數傳遞所有東西。一個不幸的結果是有誤導性的表面函數列表。
與定義為函數的修飾器相比,定義為類的復雜修飾器更加簡單。當一個對象創建后,__init__方法僅允許返回`None`,已創建的對象類型是不可以修改的。這意味著當一個被作為類創建后,因此使用少參模式沒有意義:最終被修飾的對象只會是由構建器調用返回的修飾對象的一個實例,并不是十分有用。因此,只需要探討在修飾器表達式中帶有參數并且修飾器__init__方法被用于修飾器構建,基于類的修飾器。
In?[9]:
```
class decorator_class(object):
def __init__(self, arg):
# this method is called in the decorator expression
print "in decorator init,", arg
self.arg = arg
def __call__(self, function):
# this method is called to do the job
print "in decorator call,", self.arg
return function
```
In?[10]:
```
deco_instance = decorator_class('foo')
```
```
in decorator init, foo
```
In?[11]:
```
@deco_instance
def function(*args, **kwargs):
print "in function,", args, kwargs
```
```
in decorator call, foo
```
In?[12]:
```
function()
```
```
in function, () {}
```
與通用規則相比([PEP 8](http://www.python.org/dev/peps/pep-0008)),將修飾器寫為類的行為更像是函數,因此,他們的名字通常是以小寫字母開頭。
在現實中,創建一個新類只有一個返回原始函數的修飾器是沒有意義的。人們認為對象可以保留狀態,當修飾器返回新的對象時,這個修飾器更加有用。
In?[13]:
```
class replacing_decorator_class(object):
def __init__(self, arg):
# this method is called in the decorator expression
print "in decorator init,", arg
self.arg = arg
def __call__(self, function):
# this method is called to do the job
print "in decorator call,", self.arg
self.function = function
return self._wrapper
def _wrapper(self, *args, **kwargs):
print "in the wrapper,", args, kwargs
return self.function(*args, **kwargs)
```
In?[14]:
```
deco_instance = replacing_decorator_class('foo')
```
```
in decorator init, foo
```
In?[15]:
```
@deco_instance
def function(*args, **kwargs):
print "in function,", args, kwargs
```
```
in decorator call, foo
```
In?[16]:
```
function(11, 12)
```
```
in the wrapper, (11, 12) {}
in function, (11, 12) {}
```
像這樣一個修飾器可以非常漂亮的做任何事,因為它可以修改原始的函數對象和參數,調用或不調用原始函數,向后修改返回值。
### 2.1.2.3 復制原始函數的文檔字符串和其他屬性
當修飾器返回一個新的函數來替代原始的函數時,一個不好的結果是原始的函數名、原始的文檔字符串和原始參數列表都丟失了。通過設置__doc__(文檔字符串)、__module__和__name__(完整的函數),以及__annotations__(關于參數和返回值的額外信息,在Python中可用)可以部分”移植“這些原始函數的屬性到新函數的設定。這可以通過使用[functools.update_wrapper](http://docs.python.org/2.7/library/functools.html#functools.update_wrapper)來自動完成。
In?[?]:In?[17]:
```
import functools
def better_replacing_decorator_with_args(arg):
print "defining the decorator"
def _decorator(function):
print "doing decoration,", arg
def _wrapper(*args, **kwargs):
print "inside wrapper,", args, kwargs
return function(*args, **kwargs)
return functools.update_wrapper(_wrapper, function)
return _decorator
@better_replacing_decorator_with_args("abc")
def function():
"extensive documentation"
print "inside function"
return 14
```
```
defining the decorator
doing decoration, abc
```
In?[18]:
```
function
```
Out[18]:
```
<function __main__.function>
```
In?[19]:
```
print function.__doc__
```
```
extensive documentation
```
在屬性列表中缺少了一個重要的東西:參數列表,這些屬性可以復制到替換的函數。參數的默認值可以用`__defaults__`、`__kwdefaults__`屬性來修改,但是,不幸的是參數列表本身不能設置為屬性。這意味著`help(function)`將顯示無用的參數列表,對于函數用戶造成困擾。一種繞過這個問題的有效但丑陋的方法是使用`eval`來動態創建一個封裝器。使用外部的`decorator`模塊可以自動完成這個過程。它提供了對`decorator`裝飾器的支持,給定一個封裝器將它轉變成保留函數簽名的裝飾器。
總結一下,裝飾器通常應該用`functools.update_wrapper`或其他方式來復制函數屬性。
### 2.1.2.4 標準類庫中的實例
首先,應該說明,在標準類庫中有一些有用的修飾器。有三類裝飾器確實構成了語言的一部分:
* [classmethod](http://docs.python.org/2.7/library/functions.html#classmethod) 造成函數成為“類方法”,這意味著不需要創建類的實例就可以激活它。當普通的方法被激活后,解釋器將插入一個實例對象作為第一個位置參數,`self`。當類方法被激活后,類自身被作為一點參數,通常稱為`cls`。
類方法仍然可以通過類的命名空間訪問,因此,他們不會污染模塊的命名空間。類方法可以用來提供替代的構建器:
In?[1]:
```
class Array(object):
def __init__(self, data):
self.data = data
@classmethod
def fromfile(cls, file):
data = numpy.load(file)
return cls(data)
```
```
這是一個清潔器,然后使用大量的標記來`__init__`。
```
* [staticmethod](http://docs.python.org/2.7/library/functions.html#staticmethod)用來讓方法“靜態”,即,從根本上只一個普通的函數,但是可以通過類的命名空間訪問。當函數只在這個類的內部需要時(它的名字應該與_為前綴),或者當我們想要用戶認為方法是與類關聯的,盡管實施并不需要這樣。
* [property](http://docs.python.org/2.7/library/functions.html#property)是對getters和setters pythonic的答案。用`property`修飾過的方法變成了一個getter,getter會在訪問屬性時自動調用。
In?[2]:
```
class A(object):
@property
def a(self):
"an important attribute"
return "a value"
```
In?[3]:
```
A.a
```
Out[3]:
```
<property at 0x104139260>
```
In?[4]:
```
A().a
```
Out[4]:
```
'a value'
```
在這個例子中,`A.a`是只讀的屬性。它也寫入了文檔:`help(A)`包含從getter方法中拿過來的屬性的文檔字符串。將`a`定義為一個屬性允許實時計算,副作用是它變成只讀,因為沒有定義setter。
要有setter和getter,顯然需要兩個方法。從Python 2.6開始,下列語法更受歡迎:
In?[5]:
```
class Rectangle(object):
def __init__(self, edge):
self.edge = edge
@property
def area(self):
"""Computed area.
Setting this updates the edge length to the proper value.
"""
return self.edge**2
@area.setter
def area(self, area):
self.edge = area ** 0.5
```
這種方式有效是因為`property`修飾器用property對象替換getter方法。這個對象反過來有三個方法,`getter`、`setter`和`deleter`,可以作為修飾器。他們的任務是設置property對象的getter、 setter和deleter(存儲為`fget`、`fset`和`fdel`屬性)。當創建一個對象時,getter可以像上面的例子中進行設置。當定義一個setter,我們已經在`area`下有property對象,我們通過使用setter方法為它添加setter。所有的這些發生在我們創建類時。
接下來,當類的實例被創建后,property對象是特別的,當解釋器執行屬性訪問,屬性賦值或者屬性刪除時,任務被委托給property對象的方法。
為了讓每個事情都清晰,讓我們定義一個“debug”例子:
In?[6]:
```
class D(object):
@property
def a(self):
print "getting", 1
return 1
@a.setter
def a(self, value):
print "setting", value
@a.deleter
def a(self):
print "deleting"
```
In?[7]:
```
D.a
```
Out[7]:
```
<property at 0x104139520>
```
In?[8]:
```
D.a.fget
```
Out[8]:
```
<function __main__.a>
```
In?[9]:
```
D.a.fset
```
Out[9]:
```
<function __main__.a>
```
In?[10]:
```
D.a.fdel
```
Out[10]:
```
<function __main__.a>
```
In?[12]:
```
d = D() # ... varies, this is not the same `a` function
d.a
```
```
getting 1
```
Out[12]:
```
1
```
In?[13]:
```
d.a = 2
```
```
setting 2
```
In?[14]:
```
del d.a
```
```
deleting
```
In?[15]:
```
d.a
```
```
getting 1
```
Out[15]:
```
1
```
屬性是修飾語語法的極大擴展。修飾器語法的一個前提-名字不可以重復-被違背了,但是,到目前位置沒有什么事變糟了。為getter、setter和deleter方法使用相同的名字是一個好風格。
一些更新的例子包括:
* functools.lru_cache 記憶任意一個函數保持有限的arguments\:answer對緩存(Python 3.2)
* [functools.total_ordering](http://docs.python.org/2.7/library/functools.html#functools.total_ordering)是一類修飾器,根據單一的可用方法(Python 2.7)補充缺失的順序方法(**lt**, **gt**, **le**, ...)。
### 2.1.2.5 函數廢棄
假如我們想要在我們不再喜歡的函數第一次激活時在`stderr`打印廢棄警告。如果我們不像修改函數,那么我們可以使用修飾器:
In?[16]:
```
class deprecated(object):
"""Print a deprecation warning once on first use of the function.
>>> @deprecated() # doctest: +SKIP
... def f():
... pass
>>> f() # doctest: +SKIP
f is deprecated
"""
def __call__(self, func):
self.func = func
self.count = 0
return self._wrapper
def _wrapper(self, *args, **kwargs):
self.count += 1
if self.count == 1:
print self.func.__name__, 'is deprecated'
return self.func(*args, **kwargs)
```
也可以將其實施為一個函數:
In?[17]:
```
def deprecated(func):
"""Print a deprecation warning once on first use of the function.
>>> @deprecated # doctest: +SKIP
... def f():
... pass
>>> f() # doctest: +SKIP
f is deprecated
"""
count = [0]
def wrapper(*args, **kwargs):
count[0] += 1
if count[0] == 1:
print func.__name__, 'is deprecated'
return func(*args, **kwargs)
return wrapper
```
### 2.1.2.6 A while-loop刪除修飾器
假如我們有一個函數返回事物列表,這個列表由循環創建。如果我們不知道需要多少對象,那么這么做的標準方式是像這樣的:
In?[18]:
```
def find_answers():
answers = []
while True:
ans = look_for_next_answer()
if ans is None:
break
answers.append(ans)
return answers
```
只要循環體足夠緊湊,這是可以的。一旦循環體變得更加負責,就像在真實代碼中,這種方法的可讀性將很差。我們可以通過使用yield語句來簡化,不過,這樣的話,用戶需要顯性的調用列表(find_answers())。
我們可以定義一個修飾器來為我們構建修飾器:
In?[19]:
```
def vectorized(generator_func):
def wrapper(*args, **kwargs):
return list(generator_func(*args, **kwargs))
return functools.update_wrapper(wrapper, generator_func)
```
接下來我們的函數變成:
In?[?]:
```
@vectorized
def find_answers():
while True:
ans = look_for_next_answer()
if ans is None:
break
yield ans
```
### 2.1.2.7 插件注冊系統
這是一個不會修改類的類修飾器,但是,只要將它放在全局注冊域。它會掉入返回原始對象的修飾器類別中:
In?[21]:
```
class WordProcessor(object):
PLUGINS = []
def process(self, text):
for plugin in self.PLUGINS:
text = plugin().cleanup(text)
return text
@classmethod
def plugin(cls, plugin):
cls.PLUGINS.append(plugin)
@WordProcessor.plugin
class CleanMdashesExtension(object):
def cleanup(self, text):
return text.replace('—', u'\N{em dash}')
```
這里我們用修飾器來分權插件注冊。修飾器是名詞而不是動詞,因為我們用它來聲明我們的類是`WordProcessor`的一個插件。方法`plugin`只是將類添加到插件列表中。
關于這個插件本身多說一句:它用實際的Unicode的em-dash字符替換了em-dash HTML實體。它利用[unicode絕對標記](http://docs.python.org/2.7/reference/lexical_analysis.html#string-literals)來通過字符在unicode數據庫(“EM DASH”)中的名字來插入字符。如果直接插入Unicode字符,將無法從程序源文件中區分en-dash。
### 2.1.2.8 更多例子和閱讀
* [PEP 318](http://www.python.org/dev/peps/pep-0318)(函數和方法的修飾器語法)
* [PEP 3129](http://www.python.org/dev/peps/pep-3129)(類修飾器語法)
* [http://wiki.python.org/moin/PythonDecoratorLibrary](http://wiki.python.org/moin/PythonDecoratorLibrary)
* [http://docs.python.org/dev/library/functools.html](http://docs.python.org/dev/library/functools.html)
* [http://pypi.python.org/pypi/decorator](http://pypi.python.org/pypi/decorator)
* Bruce Eckel
* Decorators I: Introduction to Python Decorators
* Python Decorators II: Decorator Arguments
* Python Decorators III: A Decorator-Based Build System
## 2.1.3 上下文管理器
上下文管理器是帶有`__enter__`和`__exit__`方法的對象,在with語句中使用:
In?[?]:
```
with manager as var:
do_something(var)
```
最簡單的等價case是
In?[?]:
```
var = manager.__enter__()
try:
do_something(var)
finally:
manager.__exit__()
```
換句話說,在[PEP343](http://www.python.org/dev/peps/pep-0343)定義的上下文管理器協議,使將[try..except..finally](http://docs.python.org/2.7/reference/compound_stmts.html#try)結構中枯燥的部分抽象成一個獨立的類,而只保留有趣的`do_something`代碼塊成為可能。
1. 首先調用[__enter__](http://docs.python.org/2.7/reference/datamodel.html#object.__enter__)方法。它會返回一個值被賦值給`var`。`as`部分是可選的:如果不存在,`__enter__`返回的值將被忽略。
2. `with`下面的代碼段將被執行。就像`try`從句一樣,它要么成功執行到最后,要么[break](http://docs.python.org/2.7/reference/simple_stmts.html#break)、[continue](http://docs.python.org/2.7/reference/simple_stmts.html#continue)或者[return](http://docs.python.org/2.7/reference/simple_stmts.html#return),或者它拋出一個異常。無論哪種方式,在這段代碼結束后,都將調用[__exit__](http://docs.python.org/2.7/reference/datamodel.html#object.__exit__)。如果拋出異常,關于異常的信息會傳遞給`__exit__`,將在下一個部分描述。在一般的情況下,異常將被忽略,就像`finally`從句一樣,并且將在`__exit__`結束時重新拋出。
假如我們想要確認一下文件是否在我們寫入后馬上關閉:
In?[23]:
```
class closing(object):
def __init__(self, obj):
self.obj = obj
def __enter__(self):
return self.obj
def __exit__(self, *args):
self.obj.close()
with closing(open('/tmp/file', 'w')) as f:
f.write('the contents\n')
```
這里我們確保當`with`代碼段退出后,`f.close()`被調用。因為關閉文件是非常常見的操作,對這個的支持已經可以在`file`類中出現。它有一個__exit__方法,調用了`close`并且被自己用于上下文管理器:
In?[?]:
```
with open('/tmp/file', 'a') as f:
f.write('more contents\n')
```
`try..finally`的常用用途是釋放資源。不同的情況都是類似的實現:在`__enter__`階段,是需要資源的,在`__exit__`階段,資源被釋放,并且異常,如果拋出的話,將被傳遞。就像with文件一樣,當一個對象被使用后通常有一些自然的操作,最方便的方式是由一個內建的支持。在每一次發布中,Python都在更多的地方提供了支持:
* 所以類似文件的對象:
* [file](http://docs.python.org/2.7/library/functions.html#file) ? 自動關閉
* [fileinput](http://docs.python.org/2.7/library/fileinput.html#fileinput),[tempfile](http://docs.python.org/2.7/library/tempfile.html#tempfile) (py >= 3.2)
* [bz2.BZ2File](http://docs.python.org/2.7/library/bz2.html#bz2.BZ2File),[gzip.GzipFile](http://docs.python.org/2.7/library/gzip.html#gzip.GzipFile),[tarfile.TarFile](http://docs.python.org/2.7/library/tarfile.html#tarfile.TarFile),[zipfile.ZipFile](http://docs.python.org/2.7/library/zipfile.html#zipfile.ZipFile)
* [ftplib](http://docs.python.org/2.7/library/ftplib.html#ftplib),[nntplib](http://docs.python.org/2.7/library/nntplib.html#nntplib) ? 關閉連接 (py >= 3.2 或 3.3)
* 鎖
* [multiprocessing.RLock](http://docs.python.org/2.7/library/multiprocessing.html#multiprocessing.RLock) ? 鎖和解鎖
* [multiprocessing.Semaphore](http://docs.python.org/2.7/library/multiprocessing.html#multiprocessing.Semaphore)
* [memoryview](http://docs.python.org/2.7/library/stdtypes.html#memoryview) ? 自動釋放 (py >= 3.2 和 2.7)
* [decimal.localcontext](http://docs.python.org/2.7/library/decimal.html#decimal.localcontext) ? 臨時修改計算的精度
* _winreg.PyHKEY ? 打開或關閉hive鍵
* warnings.catch_warnings ? 臨時殺掉警告
* contextlib.closing ? 與上面的例子類似,調用`close`
* 并行程序
* concurrent.futures.ThreadPoolExecutor ? 激活并行,然后殺掉線程池 (py >= 3.2)
* concurrent.futures.ProcessPoolExecutor ? 激活并行,然后殺掉進程池 (py >= 3.2)
* nogil ? 臨時解決GIL問題 (僅cython :( )
### 2.1.3.1 捕捉異常
當`with`代碼塊中拋出了異常,異常會作為參數傳遞給`__exit__`。與[sys.exc_info()](http://docs.python.org/2.7/library/sys.html#sys.exc_info)類似使用三個參數:type, value, traceback。當沒有異常拋出時,`None`被用于三個參數。上下文管理器可以通過從`__exit__`返回true值來“吞下”異常。可以很簡單的忽略異常,因為如果`__exit__`沒有使用`return`,并且直接運行到最后,返回`None`,一個false值,因此,異常在`__exit__`完成后重新拋出。
捕捉異常的能力開啟了一些有趣的可能性。一個經典的例子來自于單元測試-我們想要確保一些代碼拋出正確類型的異常:
In?[2]:
```
class assert_raises(object):
# based on pytest and unittest.TestCase
def __init__(self, type):
self.type = type
def __enter__(self):
pass
def __exit__(self, type, value, traceback):
if type is None:
raise AssertionError('exception expected')
if issubclass(type, self.type):
return True # swallow the expected exception
raise AssertionError('wrong exception type')
with assert_raises(KeyError):
{}['foo']
```
### 2.1.3.2 使用生成器定義上下文管理器
當討論生成器時,曾說過與循環相比,我們更偏好將生成器實現為一個類,因為,他們更短、更美妙,狀態存儲在本地,而不是實例和變量。另一方面,就如在雙向溝通中描述的,生成器和它的調用者之間的數據流動可以是雙向的。這包含異常,可以在生成器中拋出。我們希望將上下文生成器實現為一個特殊的生成器函數。實際上,生成器協議被設計成可以支持這個用例。
In?[?]:
```
@contextlib.contextmanager
def some_generator(<arguments>):
<setup>
try:
yield <value>
finally:
<cleanup>
```
[contextlib.contextmanager](http://docs.python.org/2.7/library/contextlib.html#contextlib.contextmanager)幫助者可以將一個生成器轉化為上下文管理器。生成器需要遵循一些封裝器函數強加的規則--它必須`yield`一次。在`yield`之前的部分是從`__enter__`來執行,當生成器在`yield`掛起時,由上下文管理器保護的代碼塊執行。如果拋出異常,解釋器通過`__exit__`參數將它交給封裝器,然后封裝器函數在`yield`語句的點拋出異常。通過使用生成器,上下文管理器更短和簡單。
讓我們將`closing`例子重寫為一個生成器:
In?[?]:
```
@contextlib.contextmanager
def closing(obj):
try:
yield obj
finally:
obj.close()
```
讓我們將`assert_raises`例子重寫為生成器:
In?[?]:
```
@contextlib.contextmanager
def assert_raises(type):
try:
yield
except type:
return
except Exception as value:
raise AssertionError('wrong exception type')
else:
raise AssertionError('exception expected')
```
這里我們使用修飾器來將一個生成器函數轉化為上下文管理器!
In?[1]:
```
%matplotlib inline
import numpy as np
```
- 介紹
- 1.1 科學計算工具及流程
- 1.2 Python語言
- 1.3 NumPy:創建和操作數值數據
- 1.4 Matplotlib:繪圖
- 1.5 Scipy:高級科學計算
- 1.6 獲取幫助及尋找文檔
- 2.1 Python高級功能(Constructs)
- 2.2 高級Numpy
- 2.3 代碼除錯
- 2.4 代碼優化
- 2.5 SciPy中稀疏矩陣
- 2.6 使用Numpy和Scipy進行圖像操作及處理
- 2.7 數學優化:找到函數的最優解
- 2.8 與C進行交互
- 3.1 Python中的統計學
- 3.2 Sympy:Python中的符號數學
- 3.3 Scikit-image:圖像處理
- 3.4 Traits:創建交互對話
- 3.5 使用Mayavi進行3D作圖
- 3.6 scikit-learn:Python中的機器學習