Python高級特性

轉載:http://segmentfault.com/a/1190000000498466

這章有關Python中被認爲高級的特性——就是說並不是每個語言都有的,也是說它們可能在更復雜的程序或庫中更有用,但不是說特別特殊或特別複雜。

強調這點很重要:這一章僅僅關於語言自身——關於輔之以Python的標準庫功能的特殊語法所支持的特性,不包括那些智能的外部模塊實現。

在開發Python程序語言的過程中,它的語法,獨一無二。因爲它非常透明。建議的更改通過不同的角度評估並在公開郵件列表討論,最終決定考慮到假設用例的重要性、添加更多特性的負擔,其餘語法的一致性、是否建議的變種易於讀寫和理解之間的平衡。這個過程由Python Enhancement Proposals(PEPs)的形式規範。最終這一章節中描述的特性在證明它們確實解決實際問題並且使用起來儘可能簡單後被添加。

迭代器(Iterators), 生成表達式(generator expressions)和生成器(generators)

迭代器

簡單

重複工作是浪費,將不同“土生土長”的方法替換爲標準特性換來的是更加易於閱讀和操作。

Guido van Rossum — Adding Optional Static Typing to Python
迭代器是依附於迭代協議的對象——基本意味它有一個next方法(method),當調用時,返回序列中的下一個項目。當無項目可返回時,引發(raise)StopIteration異常。

迭代對象允許一次循環。它保留單次迭代的狀態(位置),或從另一個角度講,每次循環序列都需要一個迭代對象。這意味我們可以同時迭代同一個序列不只一次。將迭代邏輯和序列分離使我們有更多的迭代方式。

調用一個容器(container)的__iter__方法創建迭代對象是掌握迭代器最直接的方式。iter函數爲我們節約一些按鍵。
>>> nums = [1,2,3]      # note that ... varies: these are different objects
>>> iter(nums)                           
<listiterator object at ...>
>>> nums.__iter__()                      
<listiterator object at ...>
>>> nums.__reversed__()                  
<listreverseiterator object at ...>

>>> it = iter(nums)
>>> next(it)            # next(obj) simply calls obj.next()
1
>>> it.next()
2
>>> next(it)
3
>>> next(it)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
當在循環中使用時,StopIteration被接受並停止循環。但通過顯式引發(invocation),我們看到一旦迭代器元素被耗盡,存取它將引發異常。

使用for...in循環也使用__iter__方法。這允許我們透明地開始對一個序列迭代。但是如果我們已經有一個迭代器,我們想在for循環中能同樣地使用它們。爲了實現這點,迭代器除了next還有一個方法__iter__來返回迭代器自身(self)。

Python中對迭代器的支持無處不在:標準庫中的所有序列和無序容器都支持。這個概念也被拓展到其它東西:例如file對象支持行的迭代。
>>> f = open('/etc/fstab')
>>> f is f.__iter__()
True
file自身就是迭代器,它的__iter__方法並不創建一個單獨的對象:僅僅單線程的順序讀取被允許。

生成表達式

第二種創建迭代對象的方式是通過 生成表達式(generator expression) ,列表推導(list comprehension)的基礎。爲了增加清晰度,生成表達式總是封裝在括號或表達式中。如果使用圓括號,則創建了一個生成迭代器(generator iterator)。如果是方括號,這一過程被‘短路’我們獲得一個列表list。

>>> (i for i in nums)                    
<generator object <genexpr> at 0x...>
>>> [i for i in nums]
[1, 2, 3]
>>> list(i for i in nums)
[1, 2, 3]
在Python 2.7和 3.x中列表表達式語法被擴展到 字典和集合表達式。一個集合set當生成表達式是被大括號封裝時被創建。一個字典dict在表達式包含key:value形式的鍵值對時被創建:

>>> {i for i in range(3)}   
set([0, 1, 2])
>>> {i:i**2 for i in range(3)}   
{0: 0, 1: 1, 2: 4}
如果您不幸身陷古老的Python版本中,這個語法有點糟:
>>> set(i for i in 'abc')
set(['a', 'c', 'b'])
>>> dict((i, ord(i)) for i in 'abc')
{'a': 97, 'c': 99, 'b': 98}
生成表達式相當簡單,不用多說。只有一個陷阱值得提及:在版本小於3的Python中索引變量(i)會泄漏。

生成器

生成器

生成器是產生一列結果而不是單一值的函數。

David Beazley — A Curious Course on Coroutines and Concurrency

第三種創建迭代對象的方式是調用生成器函數。一個 生成器(generator) 是包含關鍵字yield的函數。值得注意,僅僅是這個關鍵字的出現完全改變了函數的本質:yield語句不必引發(invoke),甚至不必可接觸。但讓函數變成了生成器。當一個函數被調用時,其中的指令被執行。而當一個生成器被調用時,執行在其中第一條指令之前停止。生成器的調用創建依附於迭代協議的生成器對象。就像常規函數一樣,允許併發和遞歸調用。
當next被調用時,函數執行到第一個yield。每次遇到yield語句獲得一個作爲next返回的值,在yield語句執行後,函數的執行又被停止。

>>> def f():
...   yield 1
...   yield 2
>>> f()                                   
<generator object f at 0x...>
>>> gen = f()
>>> gen.next()
1
>>> gen.next()
2
>>> gen.next()
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
StopIteration
讓我們遍歷單個生成器函數調用的整個歷程。

>>> def f():
...   print("-- start --")
...   yield 3
...   print("-- middle --")
...   yield 4
...   print("-- finished --")
>>> gen = f()
>>> next(gen)
-- start --
3
>>> next(gen)
-- middle --
4
>>> next(gen)                            
-- finished --
Traceback (most recent call last):
 ...
StopIteration
相比常規函數中執行f()立即讓print執行,gen不執行任何函數體中語句就被賦值。只有當gen.next()被next調用,直到第一個yield部分的語句才被執行。第二個語句打印-- middle --並在遇到第二個yield時停止執行。第三個next打印-- finished --並且到函數末尾,因爲沒有yield,引發了異常。

當函數yield之後控制返回給調用者後發生了什麼?每個生成器的狀態被存儲在生成器對象中。從這點看生成器函數,好像它是運行在單獨的線程,但這僅僅是假象:執行是嚴格單線程的,但解釋器保留和存儲在下一個值請求之間的狀態。

爲何生成器有用?正如關於迭代器這部分強調的,生成器函數只是創建迭代對象的又一種方式。一切能被yield語句完成的東西也能被next方法完成。然而,使用函數讓解釋器魔力般地創建迭代器有優勢。一個函數可以比需要next和__iter__方法的類定義短很多。更重要的是,相比不得不對迭代對象在連續next調用之間傳遞的實例(instance)屬性來說,生成器的作者能更簡單的理解侷限在局部變量中的語句。

還有問題是爲何迭代器有用?當一個迭代器用來驅動循環,循環變得簡單。迭代器代碼初始化狀態,決定是否循環結束,並且找到下一個被提取到不同地方的值。這凸顯了循環體——最值得關注的部分。除此之外,可以在其它地方重用迭代器代碼。

雙向通信

每個yield語句將一個值傳遞給調用者。這就是爲何PEP 255引入生成器(在Python2.2中實現)。但是相反方向的通信也很有用。一個明顯的方式是一些外部(extern)語句,或者全局變量或共享可變對象。通過將先前無聊的yield語句變成表達式,直接通信因PEP 342成爲現實(在2.5中實現)。當生成器在yield語句之後恢復執行時,調用者可以對生成器對象調用一個方法,或者傳遞一個值 給 生成器,然後通過yield語句返回,或者通過一個不同的方法向生成器注入異常。

第一個新方法是send(value),類似於next(),但是將value傳遞進作爲yield表達式值的生成器中。事實上,g.next()和g.send(None)是等效的。

第二個新方法是throw(type, value=None, traceback=None),等效於在yield語句處

raise type, value, traceback
不像raise(從執行點立即引發異常),throw()首先恢復生成器,然後僅僅引發異常。選用單次throw就是因爲它意味着把異常放到其它位置,並且在其它語言中與異常有關。

當生成器中的異常被引發時發生什麼?它可以或者顯式引發,當執行某些語句時可以通過throw()方法注入到yield語句中。任一情況中,異常都以標準方式傳播:它可以被except和finally捕獲,或者造成生成器的中止並傳遞給調用者。

因完整性緣故,值得提及生成器迭代器也有close()方法,該方法被用來讓本可以提供更多值的生成器立即中止。它用生成器的__del__方法銷燬保留生成器狀態的對象。

讓我們定義一個只打印出通過send和throw方法所傳遞東西的生成器。
>>> 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

>>> it = g()
>>> next(it)
--start--
--yielding 0--
0
>>> it.send(11)
--yield returned 11--
--yielding 1--
1
>>> it.throw(IndexError)
--yield raised IndexError()--
--yielding 2--
2
>>> it.close()
--closing--
注意: next還是__next__?

在Python 2.x中,接受下一個值的迭代器方法是next,它通過全局函數next顯式調用,意即它應該調用__next__。就像全局函數iter調用__iter__。這種不一致在Python 3.x中被修復,it.next變成了it.__next__。對於其它生成器方法——send和throw情況更加複雜,因爲它們不被解釋器隱式調用。然而,有建議語法擴展讓continue帶一個將被傳遞給循環迭代器中send的參數。如果這個擴展被接受,可能gen.send會變成gen.__send__。最後一個生成器方法close顯然被不正確的命名了,因爲它已經被隱式調用。

鏈式生成器

注意: 這是PEP 380的預覽(還未被實現,但已經被Python3.3接受)

比如說我們正寫一個生成器,我們想要yield一個第二個生成器——一個子生成器(subgenerator)——生成的數。如果僅考慮產生(yield)的值,通過循環可以不費力的完成:
subgen = some_other_generator()
for v in subgen:
    yield v

然而,如果子生成器需要調用send()、throw()和close()和調用者適當交互的情況下,事情就複雜了。yield語句不得不通過類似於前一章節部分定義的try...except...finally結構來保證“調試”生成器函數。這種代碼在PEP 380中提供,現在足夠拿出將在Python 3.3中引入的新語法了:

yield from some_other_generator()
像上面的顯式循環調用一樣,重複從some_other_generator中產生值直到沒有值可以產生,但是仍然向子生成器轉發send、throw和close。

裝飾器

總結

這個語言中令人激動的特性幾乎充滿歉意的,考慮到它可能沒這麼有用。

Bruce Eckel — An Introduction to Python Decorators
因爲函數或類都是對象,它們也能被四處傳遞。它們又是可變對象,可以被更改。在函數或類對象創建後但綁定到名字前更改之的行爲爲裝飾(decorator)。

“裝飾器”後隱藏了兩種意思——一是函數起了裝飾作用,例如,執行真正的工作,另一個是依附於裝飾器語法的表達式,例如,at符號和裝飾函數的名稱。

函數可以通過函數裝飾器語法裝飾:
@decorator             # ②
def function():        # ①
    pass
* 函數以標準方式定義。①
* 以@做爲定義爲裝飾器函數前綴的表達式②。在 @ 後的部分必須是簡單的表達式,通常只是函數或類的名字。這一部分先求值,在下面的定義的函數準備好後,裝飾器被新定義的函數對象作爲單個參數調用。裝飾器返回的值附着到被裝飾的函數名。

裝飾器可以應用到函數和類上。對類語義很明晰——類定義被當作參數來調用裝飾器,無論返回什麼都賦給被裝飾的名字。

在裝飾器語法實現前(PEP 318),通過將函數和類對象賦給臨時變量然後顯式調用裝飾器然後將返回值賦給函數名,可以完成同樣的事。這似乎要打更多的字,也確實裝飾器函數名用了兩次同時臨時變量要用至少三次,很容易出錯。以上實例相當於:
def function(): # ①
pass
function = decorator(function) # ②

裝飾器可以堆棧(stacked)——應用的順序是從底到上或從裏到外。就是說最初的函數被當作第一次參數器的參數,無論返回什麼都被作爲第二個裝飾器的參數……無論最後一個裝飾器返回什麼都被依附到最初函數的名下。

裝飾器語法因其可讀性被選擇。因爲裝飾器在函數頭部前被指定,顯然不是函數體的一部分,它只能對整個函數起作用。以@爲前綴的表達式又讓它明顯到不容忽視(根據PEP叫在您臉上……:))。當多個裝飾器被應用時,每個放在不同的行非常易於閱讀。

代替和調整原始對象

裝飾器可以或者返回相同的函數或類對象或者返回完全不同的對象。第一種情況中,裝飾器利用函數或類對象是可變的添加屬性,例如向類添加文檔字符串(docstring).裝飾器甚至可以在不改變對象的情況下做有用的事,例如在全局註冊表中註冊裝飾的類。在第二種情況中,簡直無所不能:當什麼不同的東西取代了被裝飾的類或函數,新對象可以完全不同。然而這不是裝飾器的目的:它們意在改變裝飾對象而非做不可預料的事。因此當一個函數在裝飾時被完全替代成不同的函數時,新函數通常在一些準備工作後調用原始函數。同樣,當一個類被裝飾成一個新類時,新類通常源於被裝飾類。當裝飾器的目的是“每次都”做什麼,像記錄每次對被裝飾函數的調用,只有第二類裝飾器可用。另一方面,如果第一類足夠了,最好使用它因爲更簡單。

實現類和函數裝飾器

對裝飾器惟一的要求是它能夠單參數調用。這意味着裝飾器可以作爲常規函數或帶有__call__方法的類的實現,理論上,甚至lambda函數也行。

讓我們比較函數和類方法。裝飾器表達式(@後部分)可以只是名字。只有名字的方法很好(打字少,看起來整潔等),但是隻有當無需用參數定製裝飾器時纔可能。被寫作函數的裝飾器可以用以下兩種方式:

>>> def simple_decorator(function):
...   print "doing decoration"
...   return function
>>> @simple_decorator
... def function():
...   print "inside function"
doing decoration
>>> function()
inside function

>>> 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
>>> function()
inside function
這兩個裝飾器屬於返回被裝飾函數的類別。如果它們想返回新的函數,需要額外的嵌套,最糟的情況下,需要三層嵌套。
>>> 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
>>> function(11, 12)
inside wrapper, (11, 12) {}
inside function, (11, 12) {}
14
_wrapper函數被定義爲接受所有位置和關鍵字參數。通常我們不知道哪些參數被裝飾函數會接受,所以wrapper將所有東西都創遞給被裝飾函數。一個不幸的結果就是顯式參數很迷惑人。

相比定義爲函數的裝飾器,定義爲類的複雜裝飾器更簡單。當對象被創建,__init__方法僅僅允許返回None,創建的對象類型不能更改。這意味着當裝飾器被定義爲類時,使用無參數的形式沒什麼意義:最終被裝飾的對象只是裝飾類的一個實例而已,被構建器(constructor)調用返回,並不非常有用。討論在裝飾表達式中給出參數的基於類的裝飾器,__init__方法被用來構建裝飾器。
>>> 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
>>> deco_instance = decorator_class('foo')
in decorator init, foo
>>> @deco_instance
... def function(*args, **kwargs):
...   print "in function,", args, kwargs
in decorator call, foo
>>> function()
in function, () {}
相對於正常規則(PEP 8)由類寫成的裝飾器表現得更像函數,因此它們的名字以小寫字母開始。

事實上,創建一個僅返回被裝飾函數的新類沒什麼意義。對象應該有狀態,這種裝飾器在裝飾器返回新對象時更有用。
>>> 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)
>>> deco_instance = replacing_decorator_class('foo')
in decorator init, foo
>>> @deco_instance
... def function(*args, **kwargs):
...   print "in function,", args, kwargs
in decorator call, foo
>>> function(11, 12)
in the wrapper, (11, 12) {}
in function, (11, 12) {}
像這樣的裝飾器可以做任何事,因爲它能改變被裝飾函數對象和參數,調用被裝飾函數或不調用,最後改變返回值。

複製原始函數的文檔字符串和其它屬性

當新函數被返回代替裝飾前的函數時,不幸的是原函數的函數名,文檔字符串和參數列表都丟失了。這些屬性可以部分通過設置__doc__(文檔字符串),__module__和__name__(函數的全稱)、__annotations__(Python 3中關於參數和返回值的額外信息)移植到新函數上,這些工作可通過functools.update_wrapper自動完成。
>>> 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
>>> function                           
<function function at 0x...>
>>> print function.__doc__
extensive documentation
一件重要的東西是從可遷移屬性列表中所缺少的:參數列表。參數的默認值可以通過__defaults__、__kwdefaults__屬性更改,但是不幸的是參數列表本身不能被設置爲屬性。這意味着help(function)將顯式無用的參數列表,使使用者迷惑不已。一個解決此問題有效但是醜陋的方式是使用eval動態創建wrapper。可以使用外部external模塊自動實現。它提供了對decorator裝飾器的支持,該裝飾器接受wrapper並將之轉換成保留函數簽名的裝飾器。

綜上,裝飾器應該總是使用functools.update_wrapper或者其它方式賦值函數屬性。

標準庫中的示例

首先要提及的是標準庫中有一些實用的裝飾器,有三種裝飾器:

classmethod讓一個方法變成“類方法”,即它能夠無需創建實例調用。當一個常規方法被調用時,解釋器插入實例對象作爲第一個參數self。當類方法被調用時,類本身被給做第一個參數,一般叫cls。

類方法也能通過類命名空間讀取,所以它們不必污染模塊命名空間。類方法可用來提供替代的構建器(constructor):

class Array(object):
    def __init__(self, data):
        self.data = data

    @classmethod
    def fromfile(cls, file):
        data = numpy.load(file)
        return cls(data)
這比用一大堆標記的__init__簡單多了。

staticmethod應用到方法上讓它們“靜態”,例如,本來一個常規函數,但通過類命名空間存取。這在函數僅在類中需要時有用(它的名字應該以_爲前綴),或者當我們想要用戶以爲方法連接到類時也有用——雖然對實現本身不必要。

property是對getter和setter問題Python風格的答案。通過property裝飾的方法變成在屬性存取時自動調用的getter。

>>> class A(object):
...   @property
...   def a(self):
...     "an important attribute"
...     return "a value"
>>> A.a                                   
<property object at 0x...>
>>> A().a
'a value'
例如A.a是隻讀屬性,它已經有文檔了:help(A)包含從getter方法獲取的屬性a的文檔字符串。將a定義爲property使它能夠直接被計算,並且產生只讀的副作用,因爲沒有定義任何setter。

爲了得到setter和getter,顯然需要兩個方法。從Python 2.6開始首選以下語法:
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。它們的作用就是設定屬性對象的getter、setter和deleter(被存儲爲fget、fset和fdel屬性(attributes))。當創建對象時,getter可以像上例一樣設定。當定義setter時,我們已經在area中有property對象,可以通過setter方法向它添加setter,一切都在創建類時完成。

之後,當類實例創建後,property對象和特殊。當解釋器執行屬性存取、賦值或刪除時,其執行被下放給property對象的方法。

爲了讓一切一清二楚[^5],讓我們定義一個“調試”例子:
<pre name="code" class="python">>>> 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"
>>> D.a                                    
<property object at 0x...>
>>> D.a.fget                               
<function a at 0x...>
>>> D.a.fset                               
<function a at 0x...>
>>> D.a.fdel                               
<function a at 0x...>
>>> d = D()               # ... varies, this is not the same `a` function
>>> d.a
getting 1
1
>>> d.a = 2
setting 2
>>> del d.a
deleting
>>> d.a
getting 1
1


屬性(property)是對裝飾器語法的一點擴展。使用裝飾器的一大前提——命名不重複——被違反了,但是目前沒什麼更好的發明。爲getter,setter和deleter方法使用相同的名字還是個好的風格。

一些其它更新的例子包括:

functools.lru_cache記憶任意維持有限 參數:結果 對的緩存函數(Python
3.2)
functools.total_ordering是一個基於單個比較方法而填充丟失的比較(ordering)方法(__lt__,__gt__,__le__等等)的類裝飾器。

函數的廢棄

比如說我們想在第一次調用我們不希望被調用的函數時在標準錯誤打印一個廢棄函數警告。如果我們不想更改函數,我們可用裝飾器

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)

也可以實現成函數:

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

while-loop移除裝飾器

例如我們有個返回列表的函數,這個列表由循環創建。如果我們不知道需要多少對象,實現這個的標準方法如下:

def find_answers():
    answers = []
    while True:
        ans = look_for_next_answer()
        if ans is None:
            break
        answers.append(ans)
    return answers

只要循環體很緊湊,這很好。一旦事情變得更復雜,正如真實的代碼中發生的那樣,這就很難讀懂了。我們可以通過yield語句簡化它,但之後用戶不得不顯式調用嗯list(find_answers())。

我們可以創建一個爲我們構建列表的裝飾器:

def vectorized(generator_func):
    def wrapper(*args, **kwargs):
        return list(generator_func(*args, **kwargs))
    return functools.update_wrapper(wrapper, generator_func)
然後函數變成這樣:
@vectorized
def find_answers():
    while True:
        ans = look_for_next_answer()
        if ans is None:
            break
        yield ans

插件註冊系統


這是一個僅僅把它放進全局註冊表中而不更改類的類裝飾器,它屬於返回被裝飾對象的裝飾器。
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中的破折號符號替代HTML中的破折號。它利用unicode literal notation通過它在unicode數據庫中的名稱(“EM DASH”)插入一個符號。如果直接插入Unicode符號,將不可能區分所插入的和源程序中的破折號。

更多例子和參考

上下文管理器

上下文管理器是可以在with語句中使用,擁有__enter__和__exit__方法的對象。

with manager as var:
    do_something(var)
相當於以下情況的簡化:
var = manager.__enter__()
try:
    do_something(var)
finally:
    manager.__exit__()

換言之,PEP 343中定義的上下文管理器協議允許將無聊的try...except...finally結構抽象到一個單獨的類中,僅僅留下關注的do_something部分。

1、__enter__方法首先被調用。它可以返回賦給var的值。as部分是可選的:如果它不出現,enter的返回值簡單地被忽略。
with語句下的代碼被執行。就像try子句,它們或者成功執行到底,或者break,continue或return,或者可以拋出異常。無論哪種情況,該塊結束後,2、__exit__方法被調用。如果拋出異常,異常信息被傳遞給__exit__,這將在下一章節討論。通常情況下,異常可被忽略,就像在finally子句中一樣,並且將在__exit__結束後重新拋出。
比如說我們想確認一個文件在完成寫操作之後被立即關閉:

>>> 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,並且本身可作爲上下文管理器。

>>> with open('/tmp/file', 'a') as f:
...   f.write('more contents\n')
try...finally常見的用法是釋放資源。各種不同的情況實現相似:在__enter__階段資源被獲得,在__exit__階段釋放,如果拋出異常也被傳遞。正如文件操作,往往這是對象使用後的自然操作,內置支持使之很方便。每一個版本,Python都在更多的地方提供支持。
  • 所有類似文件的對象:

    • file ➔ 自動關閉
    • fileinput,tempfile(py >= 3.2)
    • bz2.BZ2Filegzip.GzipFile,
      tarfile.TarFile,zipfile.ZipFile
    • ftplibnntplib ➔ 關閉連接(py >= 3.2)


    • multiprocessing.RLock ➔ 鎖定和解鎖
    • multiprocessing.Semaphore
    • memoryview ➔ 自動釋放(py >= 3.2 或 3.3)
  • decimal.localcontext➔ 暫時更改計算精度
  • _winreg.PyHKEY ➔ 打開和關閉Hive Key
  • warnings.catch_warnings ➔ 暫時殺死(kill)警告
  • contextlib.closing ➔ 如上例,調用close
  • 並行編程

    • concurrent.futures.ThreadPoolExecutor ➔
      並行調用然後殺掉線程池(py >= 3.2)
    • concurrent.futures.ProcessPoolExecutor ➔
      並行調用並殺死進程池(py >= 3.2)
    • nogil ➔ 暫時解決GIL問題(僅僅cyphon :()

捕獲異常

當一個異常在with塊中拋出時,它作爲參數傳遞給__exit__。三個參數被使用,和sys.exc_info()返回的相同:類型、值和回溯(traceback)。當沒有異常拋出時,三個參數都是None。上下文管理器可以通過從__exit__返回一個真(True)值來“吞下”異常。例外可以輕易忽略,因爲如果__exit__不使用return直接結束,返回None——一個假(False)值,之後在__exit__結束後重新拋出。

捕獲異常的能力創造了有意思的可能性。一個來自單元測試的經典例子——我們想確保一些代碼拋出正確種類的異常:

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']

使用生成器定義上下文管理器

當討論生成器時,據說我們相比實現爲類的迭代器更傾向於生成器,因爲它們更短小方便,狀態被局部保存而非實例和變量中。另一方面,正如雙向通信章節描述的那樣,生成器和它的調用者之間的數據流可以是雙向的。包括異常,可以直接傳遞給生成器。我們想將上下文管理器實現爲特殊的生成器函數。事實上,生成器協議被設計成支持這個用例。

@contextlib.contextmanager
def some_generator(<arguments>):
    <setup>
    try:
        yield <value>
    finally:
        <cleanup>
contextlib.contextmanager裝飾一個生成器並轉換爲上下文管理器。生成器必須遵循一些被包裝(wrapper)函數強制執行的法則——最重要的是它至少yield一次。yield之前的部分從__enter__執行,上下文管理器中的代碼塊當生成器停在yield時執行,剩下的在__exit__中執行。如果異常被拋出,解釋器通過__exit__的參數將之傳遞給包裝函數,包裝函數於是在yield語句處拋出異常。通過使用生成器,上下文管理器變得更短小精煉。

讓我們用生成器重寫closing的例子:
@contextlib.contextmanager
def closing(obj):
    try:
        yield obj
    finally:
        obj.close()

再把assert_raises改寫成生成器:
@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')
這裏我們用裝飾器將生成函數轉化爲上下文管理器!


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章