2009-12-31 119 views
182

我在某人的代碼中看到了這個。這是什麼意思?解釋Python的'__enter__'和'__exit__'

def __enter__(self): 
     return self 

    def __exit__(self, type, value, tb): 
     self.stream.close() 

from __future__ import with_statement#for python2.5 

class a(object): 
    def __enter__(self): 
     print 'sss' 
     return 'sss111' 
    def __exit__(self ,type, value, traceback): 
     print 'ok' 
     return False 

with a() as s: 
    print s 


print s 
+12

這裏一個很好的解釋:http://effbot.org/ zone/python-with-statement.htm – Manur 2012-08-13 09:29:54

+0

@StevenVascellaro編輯問題的代碼通常是一個糟糕的主意,**尤其是當代碼中存在錯誤時。這個問題在Py2中被提出,並且沒有理由將它更新到Py3。 – jpaugh 2018-02-05 19:22:10

回答

229

使用這些魔術方法(__enter____exit__)可以讓你實現它可以方便地與with語句中使用的對象。

這個想法是,它可以輕鬆構建需要執行一些'cleandown'代碼的代碼(將其視爲try-finally塊)。 Some more explanation here

(一旦相應的「with'語句超出範圍,然後自動地關閉連接)的有用的例子可以是一個數據庫連接對象:

class DatabaseConnection(object): 

    def __enter__(self): 
     # make a database connection and return it 
     ... 
     return self.dbconn 

    def __exit__(self, exc_type, exc_val, exc_tb): 
     # make sure the dbconnection gets closed 
     self.dbconn.close() 
     ... 

如上所述,使用該對象與with聲明(如果您使用的是Python 2.5,則可能需要在文件頂部執行from __future__ import with_statement)。

with DatabaseConnection() as mydbconn: 
    # do stuff 

PEP343 -- The 'with' statement'還有一個很好的寫法。

+4

可能'__enter__'應該總是返回'self',因爲只有其他類的方法可以在上下文中調用。 – ViFI 2016-07-09 12:38:06

+0

@ViFI在PEP 343中有4個'def __enter __(self)'的例子,沒有人'返回self':https://www.python.org/dev/peps/pep-0343/。你爲什麼這麼認爲? – Grief 2016-07-10 00:03:59

+2

@Grief:出於兩個原因,在我看來, 1)我將無法在'self'對象上調用其他方法,如下所示:http://stackoverflow.com/questions/38281853/should-the- return-value-for-enter-method-always-be-self-in-python 2)self.XYZ只是自我對象的一部分,並且返回句柄只是從維護的角度來看,這對我來說似乎是不恰當的。我寧願返回句柄來完成對象,然後將公共API僅提供給那些「自我」對象,我想向像 這樣的用戶提供公開的(abc.txt,'r')作爲fin:內容= fin.read()' – ViFI 2016-07-11 10:37:13

29

如果你知道什麼上下文管理器然後你就不需要更多的瞭解__enter____exit__神奇的方法。讓我們看一個非常簡單的例子。

在這個例子中,我打開myfile.txt的開放功能的幫助。 盡力/終於塊確保即使發生意外異常myfile.txt將被關閉。

fp=open(r"C:\Users\SharpEl\Desktop\myfile.txt") 
try: 
    for line in fp: 
     print(line) 
finally: 
    fp.close() 

現在我打開相同的文件與聲明:

with open(r"C:\Users\SharpEl\Desktop\myfile.txt") as fp: 
    for line in fp: 
     print(line) 

如果你在看代碼,我沒有關閉文件&沒有的try /終於塊。由於聲明自動關閉myfile.txt。你甚至可以通過調用print(fp.closed)屬性來檢查它 - 返回True

這是因爲開放函數返回的文件對象(在我的例子FP)有兩個內置方法__enter____exit__。它也被稱爲上下文管理器。 __enter__方法在塊的起始處被調用,__exit__方法在最後被調用。注意:聲明僅適用於支持上下文管理協議的對象,即他們有__enter____exit__方法。實現這兩種方法的類被稱爲上下文管理器類。

現在讓我們定義我們自己的上下文管理器類。

class Log: 
    def __init__(self,filename): 
     self.filename=filename 
     self.fp=None  
    def logging(self,text): 
     self.fp.write(text+'\n') 
    def __enter__(self): 
     print("__enter__") 
     self.fp=open(self.filename,"a+") 
     return self  
    def __exit__(self, exc_type, exc_val, exc_tb): 
     print("__exit__") 
     self.fp.close() 

with Log(r"C:\Users\SharpEl\Desktop\myfile.txt") as logfile: 
    print("Main") 
    logfile.logging("Test1") 
    logfile.logging("Test2") 

我希望現在你有兩個__enter____exit__魔術方法基本的瞭解。

16

我覺得奇怪很難找到爲__enter__ Python文檔和__exit__方法,通過谷歌搜索,所以幫助別人這裏是鏈接:

https://docs.python.org/2/reference/datamodel.html#with-statement-context-managers

object.__enter__(self)
輸入相關的運行時環境到這個對象。 with語句會將此方法的返回值綁定到語句的as子句中指定的目標(如果有)。

object.__exit__(self, exc_type, exc_value, traceback)
退出與此對象相關的運行時上下文。參數描述導致上下文退出的異常。如果上下文退出而沒有例外,則所有三個參數將爲None

如果提供了一個異常,並且該方法希望抑制該異常(即防止它被傳播),它應該返回一個真值。否則,在退出此方法後,異常將被正常處理。

請注意,__exit__()方法不應該重新傳入傳入的異常;這是來電者的責任。

我希望明確說明__exit__方法的參數。這是缺乏,但我們可以推斷他們...

推測exc_type是例外的類。

它說你不應該重新提高傳入的例外。這向我們建議其中一個參數可能是一個實際的Exception實例......或者你應該從類型和值中自己實例化它?

我們可以通過看這篇文章回答:
http://effbot.org/zone/python-with-statement.htm

例如,下面的__exit__方法吞下任何類型錯誤,而是通過讓所有其它異常:

def __exit__(self, type, value, traceback): 
    return isinstance(value, TypeError) 

。 ..so明確value是一個異常實例。

而且據推測traceback是一個Python traceback對象。

6

除了上述答案例證調用順序,一個簡單的運行示例

class myclass: 
    def __init__(self): 
     print("__init__") 

    def __enter__(self): 
     print("__enter__") 

    def __exit__(self, type, value, traceback): 
     print("__exit__") 

    def __del__(self): 
     print("__del__") 

with myclass(): 
    print("body") 

產生輸出:

__init__ 
__enter__ 
body 
__exit__ 
__del__ 
+1

即使切換了定義的順序,執行順序仍然保持不變! – Sean 2018-02-05 07:52:06