1. 程式人生 > 實用技巧 >面對物件高階之魔法方法

面對物件高階之魔法方法

一、__enter__ 和 __exit__

python中實現了__enter__和__exit__方法支援上下文管理器協議。上下文管理器就是支援上下文管理器協議的物件,它是為了with而生。當with語句在開始執行時,會在上下文管理器物件上呼叫 __enter__ 方法。with語句執行結束後,會在上下文管理器物件上呼叫 __exit__ 方法

1.with語句是什麼?

有一些任務,可能事先需要設定,事後做清理工作。對於這種場景,Python的with語句提供了一種非常方便的處理方式。一個很好的例子是檔案處理,你需要獲取一個檔案控制代碼,從檔案中讀取資料,然後關閉檔案控制代碼。

如果不用with語句,程式碼如下:

try:
    # 1. [進入]
    f = open('a.txt', 'r', encoding="utf-8")
    # 2. [執行]
    print(f.read())
finally:
    if f:
        # 3. [退出]
        f.close()

python操作檔案的流程一般就是這三步:

[進入]用只讀方式開啟檔案
如果檔案不存在,open()函式就會丟擲一個IOError的錯誤,並且給出錯誤碼和詳細的資訊告訴你檔案不存在

[執行]讀取檔案內容
如果檔案開啟成功,接下來,呼叫read()方法可以一次讀取檔案的全部內容,Python把內容讀到記憶體,用一個str物件表示

[退出]關閉開啟的檔案
檔案使用完畢後必須關閉,因為檔案物件會佔用作業系統的資源,並且作業系統同一時間能開啟的檔案數量也是有限的

思考為什麼關閉檔案操作一定要放在finallly語句裡?

由於檔案讀寫時都有可能產生IOError,一旦出錯,後面的f.close()就不會呼叫。所以,為了保證無論是否出錯都能正確地關閉檔案,我們可以使用try ... finally來實現。

發現共性:

我們發現其實這種過程化的語句有共性,比如說在進去一個片段前必須做某種超讚,處理工作後又需要執行一個結束操作。比如上面的這段程式碼:
finally:
    if f:
        f.close()

就可以做一個封裝。

使用with語句後,我們是這樣開啟一個檔案的:

with open("a.txt", "r", encoding="utf-8") as f:
    print(f.read())

這個with語句和前面的try ... finally結構是一樣的,但是程式碼更佳簡潔,並且不必呼叫f.close()方法。

2.with語句的執行原理

從直譯器的角度去理解with語句執行流程:

with語句的基本形式是:

with 表示式 as 變數:
    語句塊

這樣的一段程式碼可以稱為一個上下文(context),在執行with語句時,直譯器會先求出表示式的值,這個值(物件)是一個上下文管理器,並且這個物件擁有如下類構造方法:  

    def __enter__():
        # 描述進入上下文的動作
        pass

    def __exit__():
        # 描述退出上下文的動作
        pass

with語句在求出這個上下文管理器物件之後,自動執行進入方法,並將這個物件的返回值賦值於 as 之後的變數,然後執行語句塊。然後在退出上下文前,自動執行物件的退出方法

python系統和標準庫的一些型別定義了這對操作,可以直接用於with語句。比如檔案物件就直接支援這一對操作,因此可以用在with語句的頭部。

如果你也有類似的計算過程需要抽取出來,那麼可以自定義一個類,並且包含進入、退出方法。

用途或者說好處:

1.使用with語句的目的就是把程式碼塊放入with中執行,with結束後,自動完成清理工作,無須手動干預

2.在需要管理一些資源比如檔案,網路連線和鎖的程式設計環境中,可以在__exit__中定製自動釋放資源的機制,你無須再去關係這個問題,這將大有用處

3.實現自定義with 類

class MyClass:
    def __init__(self,file_name,mode='r',encoding='utf-8'):
        self.fine_name = file_name
        self.mode = mode
        self.encoding = encoding


    def __enter__(self):
        print("進入上下文")
        self.file = open(self.fine_name,mode=self.mode,encoding=self.encoding)
        return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("退出上下文")
        self.file.close()


with MyClass('test.txt','w',encoding='utf-8') as f:
    f.write("test msg")
print('hello world')