[翻譯]高階Python一學就會

高階Python一學就會

在前一篇文章中,我們學習了幾個一般來說比較有用的Python語言的特性。 考慮到這篇文章是前一篇文章的續集,在這裏我們進一步延伸一些顯式使用裝飾器的概念,我們並沒有擾亂前一篇文章的內容。

裝飾器

裝飾器的概念展現了python領域內最漂亮和最強大的設計可能性之一,這不僅僅是在Python編程中,也在整個軟件設計領域。本質上來說,裝飾器就是一種包裝,主要是想在不改變被包裝的原代碼的原則下,實現延伸代碼功能性的目的。爲了能讓這個概念更清晰易懂,我們來從最基礎的內容開始。

函數亦稱爲第一類對象

函數簡單來說就是基於給定的自變量返回一個值。在python中,這些函數還有另外一種榮譽稱號,叫作第一類對象。考慮到函數可以被像普通的對象一樣被作爲自變量傳遞,函數榮膺這項稱號還真的是恰如其分。比如說,它們可以被作爲自變量傳遞給其它函數,同時也可以被用做一個函數返回值。

作爲自變量的函數

def greet(name):
    print ('Hello ' + name)

def send_greetings(fun, name):
    fun(name)
    
send_greetings(greet, 'John')

閉包函數

閉包函數是定義在其它函數內部的函數。這使得:閉包函數只有在其父函數被調用時纔會被定義,或者說閉包函數的作用域僅存在於父函數內部,亦或者說閉包函數只是作爲父函數的一個局域變量存在的。

def send_greetings(name):
    def greet_message():
        return ‘Hello ‘
    result = greet_message() + name
    print (result)
send_greetings(‘John’)

從函數中返回函數

Python還允許你將一個函數作爲返回值傳給另一個函數。本質上來講,我們只是將內部函數的引用傳回以待後續的調用。

def classify(element):
    def even_number():
        print ('Element is even.')
    def odd_number():
        print ('Element is odd.')
    if element%2 == 0:
        return even_number
    else:
        return odd_number

classify(2)()

裝飾器

現在,有了上面所有這些基本概念的經驗之後,我們來這些概念串起來組成一個完整的圖像。

def my_decorator(fun):
    def wrapper():
        print (‘Before calling the function…’)
        fun()
        print (‘After calling the function…’)
    return wrapper
        
def say_hello():
    print (‘Hello!’)
    
say_hello = my_decorator(say_hello)

就是這樣!!! 這就是我們能得到的最簡單的裝飾器。我們把到前面學到的東西一個個的都用進來了。所以裝飾器其實就是

一個把另一個函數作爲自變量的函數,它生成了一個新的函數,同時對原先的函數擴展了功能性,它最終返回的是生成的新的函數,這樣我們可以在其他地方使用它。

另外,python讓編程者可以非常整潔漂亮地創建並使用裝飾器。

def my_decorator(fun):
    def wrapper():
        print (‘Before calling the function…’)
        fun()
        print (‘After calling the function…’)
    return wrapper

@my_decorator
def say_hello():
    print (‘Hello!’)

上下文環境管理

簡單來說吧,上下文管理是一種資源的獲取和釋放機制,它避免了資源泄露並確保即使在遇到一些糟糕的異常情況後仍能完成恰當的清理工作。比如說,保證文件在打開之後的關閉,鎖在獲得之後的釋放。這個概念在大量其他編程語言中有清楚地表述和恰當地使用,比如說C++ 中的RAII。

技術上來說,它是一個對象需要去遵循的通訊協議。這個協議要求,對象要像一個上下文管理器一樣,要執行 __enter____exit__ 方法。

__enter__ 返回要被管理的資源,而 __exit__ 完成任何可能出現的清理工作並且什麼也不返回。

class File:
    def __init__(self, name):
        self.name = name
    def __enter__(self):
        self.file = open(self.name, 'w')
        return self.file
    def __exit__(self, type, value, trace_back):
        if self.file:
            self.close()

現在,上面這個類可以在with語句中被安全地使用。更一般地,使用with,我們可以調用任何東西並返回一個上下文管理器。

with File('example.txt') as f:
    f.write('Hey hello')
    f.write('See you later. Bye!!!')

__enter__with語句被調用的時候被執行。當代碼塊的語句執行完了的時候 __exit__ 方法會被調用。

上下文管理器還可以被用在更復雜的問題中。我們看另一個上下文管理器幾乎不可避免的例子。這個問題中的資源就是,我們可以避免的問題就是死鎖

from threading import Lock
lock = Lock()
def do_something():
    lock.acquire()
    raise Exception('Oops I am sorry. I have to raise it!')
    lock.release()    

try:
    do_something()
except:
    print ('Got an exception.')

注意,在釋放鎖之前會報異常。這會導致一個明顯的副作用,那就是所有調用do_something的其它線程會永遠地出現擁堵從而導致系統死鎖。使用上下文管理器,我們可以避免這種尷尬的情況。

from threading import Lock
lock = Lock()
def do_something():
    with lock:
        raise Exception('Oops I am sorry. I have to raise it!')

try:
    do_something()
except:
    print ('Got an exception.')

哇塞!看到沒,即使遇到一些異常情況,代碼也能恰當地完成清理工作。很明顯,在任何可能的情況下都不會出現使用了上下文管理器來獲得鎖,而還沒有釋放鎖就結束的情形。它就應該是這樣。

連鎖的異常

設想一種情況,因爲除以零而使得方法拋出ZeroDivisionError的異常。顯然,在python中我們有很好的一組工具能夠來處理異常。
然而,如果異常的原因是因爲這個函數所調用的一些其它函數中出現了TypeError呢。僅僅報告最頂層的異常是不夠,這樣我們會丟失原始的異常信息和一連串異常的主要原因。

爲了演示這個問題中的概念,我們舉個簡單的例子。

def chained_exceptions():
try:
    raise ValueError(17)
except Exception as ex:
    raise ValueError(23) from ex
if __name__ == "__main__":
    chained_exceptions()

在python 2.0中,上述情況會導致後面的異常被拋出而前面的會丟失,像下面這樣。

Traceback (most recent call last):
File “test.py”, line 3, in chained_exceptions
    raise ValueError(23)
ValueError: 23

很明顯,我們丟失了信息中有價值的那部分,因爲我們丟失了異常導致的真正的起因。然而,在python3.0中運行同樣的腳本,我們可以看到完整的異常棧跟蹤的信息。

Traceback (most recent call last):
File “test.py”, line 3, in chained_exceptions
    raise ValueError(17)
ValueError: 17
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File “test.py”, line 8, in <module> chained_exceptions()
File “test.py”, line 5, in chained_exceptions
    raise ValueError(23) from ex
ValueError: 23

如果對您對本文的內容有任何的修改或者改進的意見,請在評論中讓我看到。
(原文鏈接:https://medium.com/quick-code/advanced-python-made-easy-2-d5a7ffb4e658)

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