Python 高級特性

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

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

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

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

迭代器

簡單github

重複工做是浪費,將不一樣「土生土長」的方法替換爲標準特性換來的是更加易於閱讀和操做。web

Guido van Rossum — Adding Optional Static Typing to Python數據庫

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

迭代對象容許一次循環。它保留單次迭代的狀態(位置),或從另外一個角度講,每次循環序列都須要一個迭代對象。這意味咱們能夠同時迭代同一個序列不僅一次。將迭代邏輯和序列分離使咱們有更多的迭代方式。編程

調用一個容器(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語句中。任一狀況中,異常都以標準方式傳播:它能夠被exceptfinally捕獲,或者形成生成器的停止並傳遞給調用者。

因完整性緣故,值得說起生成器迭代器也有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__。對於其它生成器方法——sendthrow狀況更加複雜,由於它們不被解釋器隱式調用。然而,有建議語法擴展讓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中產生值直到沒有值能夠產生,可是仍然向子生成器轉發sendthrowclose

裝飾器

總結

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

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方法,以上代碼起做用。這個對象反過來有三個可用於裝飾器的方法gettersetterdeleter。它們的做用就是設定屬性對象的getter、setter和deleter(被存儲爲fgetfsetfdel屬性(attributes))。當建立對象時,getter能夠像上例同樣設定。當定義setter時,咱們已經在area中有property對象,能夠經過setter方法向它添加setter,一切都在建立類時完成。

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

    爲了讓一切一清二楚[^5],讓咱們定義一個「調試」例子:

    >>> 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('&mdash;', 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的返回值簡單地被忽略。
  2. with語句下的代碼被執行。就像try子句,它們或者成功執行到底,或者breakcontinuereturn,或者能夠拋出異常。不管哪一種狀況,該塊結束後,__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
    • ftplib, nntplib ➔ 關閉鏈接(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')

這裏咱們用裝飾器將生成函數轉化爲上下文管理器!


原文 Advanced Python Constructs
翻譯 reverland

相關文章
相關標籤/搜索