2014-10-29 55 views
10

我用google搜索了calling __enter__ manually但沒有運氣。因此,讓我們假設我有MySQL連接器類,它使用__enter____exit__函數(最初與with語句一起使用)連接/從數據庫斷開連接。手動調用__enter__和__exit__

而且讓我們有一個使用其中2個連接的類(例如用於數據同步)。 注意:這不是我的真實生活場景,但它似乎是最簡單的示例

,使其所有一起工作的最簡單方法是階級是這樣的:

class DataSync(object): 

    def __init__(self): 
     self.master_connection = MySQLConnection(param_set_1) 
     self.slave_connection = MySQLConnection(param_set_2) 

    def __enter__(self): 
      self.master_connection.__enter__() 
      self.slave_connection.__enter__() 
      return self 

    def __exit__(self, exc_type, exc, traceback): 
      self.master_connection.__exit__(exc_type, exc, traceback) 
      self.slave_connection.__exit__(exc_type, exc, traceback) 

    # Some real operation functions 

# Simple usage example 
with DataSync() as sync: 
    records = sync.master_connection.fetch_records() 
    sync.slave_connection.push_records(records) 

Q:這是好(有什麼不對)來調用手動__enter__/__exit__這樣嗎?

Pylint 1.1.0沒有對此發出任何警告,也沒有發現任何有關它的文章(谷歌鏈接在開始)。

而關於調用什麼:

try: 
    # Db query 
except MySQL.ServerDisconnectedException: 
    self.master_connection.__exit__(None, None, None) 
    self.master_connection.__enter__() 
    # Retry 

這是一個好/壞的做法?爲什麼?

+4

我會說其優良的,看到的[我們都同意的成年人在這裏(https://mail.python.org/pipermail/tutor/2003-October/025932.html),或者你可以使用類似[ExitStack](https://docs.python.org/3/library/contextlib.html#contextlib.ExitStack),它會爲你打電話。 – matsjoyce 2014-10-29 16:31:11

+0

無論如何,在with語句中都會調用\ _ \ _ exit \ _ \ _方法,而手動調用這些方法時不會這樣。 – XORcist 2014-10-29 16:35:04

+0

@XORcist我已經添加了示例用法示例...在提供的案例中(我相信)您必須手動調用它。 – Vyktor 2014-10-29 16:38:39

回答

8

不,沒有什麼問題。甚至在標準庫中有這樣的地方。像multiprocessing module

class SemLock(object): 

    def __init__(self, kind, value, maxvalue, *, ctx): 
      ... 
      try: 
       sl = self._semlock = _multiprocessing.SemLock(
        kind, value, maxvalue, self._make_name(), 
        unlink_now) 
      except FileExistsError: 
       pass 
    ... 

    def __enter__(self): 
     return self._semlock.__enter__() 

    def __exit__(self, *args): 
     return self._semlock.__exit__(*args) 

還是tempfile module

class _TemporaryFileWrapper: 

    def __init__(self, file, name, delete=True): 
     self.file = file 
     self.name = name 
     self.delete = delete 
     self._closer = _TemporaryFileCloser(file, name, delete) 

    ... 

    # The underlying __enter__ method returns the wrong object 
    # (self.file) so override it to return the wrapper 
    def __enter__(self): 
     self.file.__enter__() 
     return self 

    # Need to trap __exit__ as well to ensure the file gets 
    # deleted when used in a with statement 
    def __exit__(self, exc, value, tb): 
     result = self.file.__exit__(exc, value, tb) 
     self.close() 
     return result 

標準庫例子並不要求兩個對象__enter__/__exit__,但如果你已經有了一個對象,它是負責創建/摧毀多個對象而不是一個對象的上下文,所有這些對象調用__enter__/__exit__都沒問題。

唯一潛在的問題是正確處理要管理的對象的__enter____exit__調用的返回值。使用__enter__,您需要確保您返回的包裝對象的用戶需要state才能從with ... as <state>:調用返回。使用__exit__,您需要決定是否要傳播發生在上下文中的任何異常(通過返回False),或者抑制它(通過返回True)。你的託管對象可能會試圖做到這一點,你需要決定什麼對包裝器對象有意義。

7

你的第一個例子是不是一個好主意:

  1. 會發生什麼,如果slave_connection.__enter__拋出異常:

    • master_connection獲得其資源
    • slave_connection失敗
    • DataSync.__enter__ propogates異常
    • DataSync.__exit__不運行
    • master_connection永遠不會清理!如果master_connection.__exit__拋出一個異常
    • 潛在的壞事情
  2. 會發生什麼?

    • DataSync.__exit__完早
    • slave_connection是從來沒有清理完畢!
    • 潛在的壞事情

contextlib.ExitStack可以幫助在這裏:

def __enter__(self): 
    with ExitStack() as stack: 
     stack.enter_context(self.master_connection) 
     stack.enter_context(self.slave_connection) 
     self._stack = stack.pop_all() 
    return self 

def __exit__(self, exc_type, exc, traceback): 
    self._stack.__exit__(self, exc_type, exc, traceback) 

問同樣的問題:

  1. 如果會發生什麼10拋出異常:

    • 將與退出塊,並stack清理master_connection
    • 一切OK!
  2. 如果master_connection.__exit__引發異常,會發生什麼情況?

    • 沒關係,slave_connection獲取此之前清理被稱爲
    • 一切OK!
  3. 好的,如果slave_connection.__exit__引發異常會發生什麼?

    • ExitStack確保調用master_connection.__exit__無論發生什麼事,以從連接
    • 一切OK!

有什麼不妥直接調用__enter__的,但如果你需要調用它在一個以上的對象,請確保你收拾妥當!

+0

我沒有提到我在那時運行了Python 3.2,並根據['contextlib.ExitStack'文檔](https://docs.python.org/3/library/contextlib.html#contextlib.ExitStack)添加了它在'3.3'中,所以當時dano最準確,但我同意3.3+這是這樣做的正確方法。 – Vyktor 2016-08-29 08:38:48

+2

@Vyktor:早期版本總是有['contextlib2'](https://pypi.python.org/pypi/contextlib2)backport!即使在ExitStack之前,您可以通過一些小心翼翼的嘗試/終止來實現安全 – Eric 2016-08-29 16:02:44