1. 程式人生 > >Python with/as 環境管理器

Python with/as 環境管理器

with語句是從Python 2.5開始引入的一種與異常處理相關的功能(2.5版本中要通過 from __future__ import with_statement匯入後才可以使用,
從2.6 版本開始預設可用。
with/as語句的設計是用於作為常見的try/finally用法模式的替代方案,確保執行一些必要的終止或者清理工作。python以環境管理器強化一些內建工具,
例如自動關閉檔案,執行緒中的自動上鎖和解鎖等,程式設計師也可以用類來編寫自己的環境管理器(__enter__, __exit__)。

with語句的基本用法

with expression [as variable]:
    """
    with_block
    put your code here
    """
... code ...

expression要返回一個物件,從而支援環境管理協議(下面會談到這個協議)。
as語句是選用的,expression語句會返回一個值,這個值會賦值給as後面的variable變數。(注意不是把這個物件賦值給variable變數,是把這個物件返回
一個值賦值給variable,這個後面會談到)

python的一些內建物件已經支援了環境管理協議,如檔案物件。使用with語句可以在with_block之後自動關閉檔案,無論是否發生異常。

for example:

with open("test_file.txt", "r") as myfile:
    for
line in myfile: print(line)

這裡開啟檔案物件後,返回了被開啟檔案物件的例項並賦給了myfile變數。對myfile處理完成後,with語句會自動執行myfile.close()。

相當於try/finally語句:

myfile = open("test_file.txt", "r")
try:
    for line in myfile:
        print(line)
finally:
    myfile.close()

環境管理協議

python有一些內建物件已經帶有了環境管理器,如剛剛談過的檔案物件。但我們仍然可以自己編寫一個帶有環境管理器的類。
這個實際上就是類的運算子過載,需要使用__enter__

__exit__

先簡述一些with語句的實際工作流程:
1. 計算expression表示式,所得到的物件就稱為環境管理器,它必須有__enter____exit__方法。
2. 環境管理器的__enter__會首先被呼叫,如果存在as語句,它的返回值會賦給as後面的變數,否則直接丟棄。
3. with_block中的程式碼塊會執行。
4. 最後執行__exit__(type, value, traceback)方法。
- 如果with語句引發了異常,且該方法的返回值是假,那麼異常就會被重新引發,否則異常會終止。正常情況下我們希望異常被重新引發,這樣才能講異常  傳遞到with程式碼塊外面去,便於發現和處理。
- 如果with語句沒有引發異常,其中type, value, traceback引數都會以None值傳遞。

現在我們來自己定義一個環境管理器物件:

class Person:
    def __init__(self, name):
        self.name = name

    def call(self, number):
        print("{person} is calling {number}".format(person=self.name, number=number))

    def __enter__(self):
        print("{person} pick up the phone".format(person=self.name))
        print("ready to call")
        return self

    def __exit__(self, exit_type, exit_value, exit_traceback):
        try:
            if exit_type is None:
                print("ok, thers is no exception. ")
            else:
                print("there are some exceptions")
                print(exit_type, exit_value, exit_traceback)
                return False
        finally:
            print("{person} cut the call".format(person=self.name))
            # return True

# test1
with Person("Jon") as jon:
    jon.call("110")

# test 2
with Person("Jon") as jon:
    jon.call("120")
    raise ValueError("the phone number is wrong")


輸出:

# test1
Jon pick up the phone
ready to call
Jon is calling 110
ok, thers is no exception.
Jon cut the call

# test 2
Jon pick up the phone
ready to call
Jon is calling 120
there are some exceptions
(<type 'exceptions.ValueError'>, ValueError('the phone number is wrong',), <traceback object at 0x7fa717085a28>)
Jon cut the call
Traceback (most recent call last):
  File "/home/vincent/github/practice/test.py", line 35, in <module>
    raise ValueError("the phone number is wrong")
ValueError: the phone number is wrong

關於異常是否丟擲:
前面已經說過,在__exit__中如果返回假(包括False,None)等,那麼with語句中出現的異常就會被丟擲來。如上面test2的輸出一樣。
但如果return True,如我上面注掉的一樣,那麼with語句中出現的異常就會被自動過濾掉,不會丟擲來,那麼test2的輸出就該是:

Jon pick up the phone
ready to call
Jon is calling 120
there are some exceptions
(<type 'exceptions.ValueError'>, ValueError('the phone number is wrong',), <traceback object at 0x7fa717085a28>)
Jon cut the call

發現沒有,雖然with語句捕獲到了異常,我們在程式碼裡手動把它print了出來。但實際上並不會丟擲異常,也就是說程式碼會忽略這個異常繼續執行下去。
建議將異常丟擲,除非你有十足的理由。