1. 程式人生 > >整理一下python中with的用法

整理一下python中with的用法

ith替代了之前在python裡使用try...finally來做清理工作的方法。基本形式如下:

with expression [as variable]:
    with-block

當expression執行的時候,返回一個支援context management protocol(有__enter__(), __exit__()方法)的物件

這個物件的__enter__()方法在with-block執行前執行,該方法返回的結果賦給variable(如果variable存在的話)

with-block執行之後,__exit__()方法被呼叫,在這裡可以執行清理工作

 

在實際的編碼過程中,有時有一些任務,需要事先做一些設定,事後做一些清理,這時就需要python with出場了,with能夠對這樣的需求進行一個比較優雅的處理,最常用的例子就是對訪問檔案的處理。

一般訪問檔案資源時我們會這樣處理:

f = open(r'c:\test.txt', 'r')
data = f.read()
f.close()


這樣寫沒有錯,但是容易犯兩個毛病:
1. 如果在讀寫時出現異常而忘了異常處理。
2. 忘了關閉檔案控制代碼

以下的加強版本的寫法:

f = open(r'c:\test.txt', 'r')
try:
    data = f.read()
finally: f.close()

 

以上的寫法就可以避免因讀取檔案時異常的發生而沒有關閉問題的處理了。程式碼長了一些。
但使用with有更優雅的寫法:

with open(r'c:\test.txt', 'r') as f:
    data = f.read()


說明:
with後面接的物件返回的結果賦值給f。此例當中open函式返回的檔案物件賦值給了f.with會自已獲取上下檔案的異常資訊。
with是如何做到的呢?
with後面返回的物件要求必須兩__enter__()/__exit__()這兩個方法,而檔案物件f剛好是有這兩個方法的,故應用自如。
pytho中官方定義說明如下(https://docs.python.org/2/reference/datamodel.html#context-managers):

object.__enter__(self)
進入與此物件相關的執行時上下文。with語句將將此方法的返回值繫結到語句的AS子句中指定的目標(如果有設定的話)

object.__exit__(self, exc_type, exc_value, traceback)
退出與此物件相關的執行時上下文。引數描述導致上下文退出的異常。如果上下文執行時沒有異常發生,那麼三個引數都將置為None。
如果有異常發生,並且該方法希望抑制異常(即阻止它被傳播),則它應該返回True。否則,異常將在退出該方法時正常處理。

請注意, __exit__()方法不應該重新丟擲傳入的異常,這是呼叫者的職責。
下面舉例說明他的原理:

1. 無異常發生時的例子:

#!/user/bin/env python3
#-*- coding:utf-8 -*-

class Test:
    def __enter__(self):
        print('__enter__() is call!')
        return self

    def dosomething(self):
        print('dosomethong!')

    def __exit__(self, exc_type, exc_value, traceback):
        print('__exit__() is call!')
        print(f'type:{exc_type}')
        print(f'value:{exc_value}')
        print(f'trace:{traceback}')
        print('__exit()__ is call!')

with Test() as sample:
    sample.dosomething()

>>
__enter__() is call!
dosomethong!
__exit__() is call!
type:None
value:None
trace:None
__exit()__ is call!

 


以上的例項Text,我們注意到他帶有__enter__()/__exit__()這兩個方法,當物件被例項化時,就會主動呼叫__enter__()方法,任務執行完成後就會呼叫__exit__()方法,另外,注意到,__exit__()方法是帶有三個引數的(exc_type, exc_value, traceback), 依據上面的官方說明:如果上下文執行時沒有異常發生,那麼三個引數都將置為None, 這裡三個引數由於沒有發生異常,的確是置為了None, 與預期一致. 

2. 有異常發生時,會丟擲異常的例子:
以下例子在上面稍做了一些修改,讓執行時產生異常,看看這三個引數的賦值情況:

#!/user/bin/env python3
#-*- coding:utf-8 -*-

class Test:
    def __enter__(self):
        print('__enter__() is call!')
        return self

    def dosomething(self):
        x = 1/0
        print('dosomethong!')

    def __exit__(self, exc_type, exc_value, traceback):
        print('__exit__() is call!')
        print(f'type:{exc_type}')
        print(f'value:{exc_value}')
        print(f'trace:{traceback}')
        print('__exit()__ is call!')
        # return True


with Test() as sample:
    sample.dosomething()
>>
__enter__() is call!
Traceback (most recent call last):
__exit__() is call!
type:<class 'ZeroDivisionError'>
  File "C:/Users/xxx/PycharmProjects/Test1/test.py", line 23, in <module>
value:division by zero
    sample.dosomething()
trace:<traceback object at 0x000001C08CF32F88>
  File "C:/Users/xxx/PycharmProjects/Test1/test.py", line 10, in dosomething
__exit()__ is call!
    x = 1/0
ZeroDivisionError: division by zero

 


從結果可以看出, 在執行到dosomethong時就發生了異常,然後將異常傳給了__exit__(), 依據上面的官方說明:如果有異常發生,並且該方法希望抑制異常(即阻止它被傳播),則它應該返回True。否則,異常將在退出該方法時正常處理。當前__exit__並沒有寫明返回True,故會丟擲異常,也是合理的,但是正常來講,程式應該是不希望它丟擲異常的,這也是呼叫者的職責,我們將再次修改__exit__, 將其返回設定為True, 

3. 有異常發生時,不再丟擲異常的例子:

在上面的例子上做點修改.

#!/user/bin/env python3
#-*- coding:utf-8 -*-

class Test:
    def __enter__(self):
        print('__enter__() is call!')
        return self

    def dosomething(self):
        x = 1/0
        print('dosomethong!')

    def __exit__(self, exc_type, exc_value, traceback):
        print('__exit__() is call!')
        print(f'type:{exc_type}')
        print(f'value:{exc_value}')
        print(f'trace:{traceback}')
        print('__exit()__ is call!')
        return True


with Test() as sample:
    sample.dosomething()

>>
__enter__() is call!
__exit__() is call!
type:<class 'ZeroDivisionError'>
value:division by zero
trace:<traceback object at 0x000001C94E592F88>
__exit()__ is call!

 


從結果看,異常丟擲被抑制了,符合預期。
---------------------

轉載自:https://blog.csdn.net/lxy210781/article/details/81176687