[Python Study Notes]with的使用

來源:互聯網
上載者:User

標籤:final   type   als   number   lib   run   rom   __init__   代碼   

在 Python 2.5 中, with 關鍵字被加入。它將常用的 try ... except ... finally ... 模式很方便的被複用。看一個最經典的例子:

with open(‘file.txt‘) as f:  content = f.read()

在這段代碼中,無論 with 中的代碼塊在執行的過程中發生任何情況,檔案最終都會被關閉。如果代碼塊在執行的過程中發生了一個異常,那麼在這個異常被拋出前,程式會先將被開啟的檔案關閉。

再看另外一個例子。

在發起一個資料庫事務請求的時候,經常會用類似這樣的代碼:

db.begin()try:  # do some actionsexcept:  db.rollback()  raisefinally:  db.commit()

如果將發起事務請求的操作變成可以支援 with 關鍵字的,那麼用像這樣的代碼就可以了:

with transaction(db):  # do some actions

下面,詳細的說明一下 with 的執行過程,並用兩種常用的方式實現上面的代碼。

with 的一般執行過程

一段基本的 with 運算式,其結構是這樣的:

with EXPR as VAR:  BLOCK

其中: EXPR 可以是任意運算式; as VAR 是可選的。其一般的執行過程是這樣的:

  1. 計算 EXPR ,並擷取一個上下文管理器。
  2. 上下文管理器的 __exit()__ 方法被儲存起來用於之後的調用。
  3. 調用上下文管理器的 __enter()__ 方法。
  4. 如果 with 運算式包含 as VAR ,那麼 EXPR 的傳回值被賦值給 VAR 。
  5. 執行 BLOCK 中的運算式。
  6. 調用上下文管理器的 __exit()__ 方法。如果 BLOCK 的執行過程中發生了一個異常導致程式退出,那麼異常的 type 、 value 和 traceback (即 sys.exc_info()的傳回值 )將作為參數傳遞給 __exit()__ 方法。否則,將傳遞三個 None 。

將這個過程用代碼錶示,是這樣的:

mgr = (EXPR)exit = type(mgr).__exit__ # 這裡沒有執行value = type(mgr).__enter__(mgr)exc = Truetry:  try:    VAR = value # 如果有 as VAR    BLOCK  except:    exc = False    if not exit(mgr, *sys.exc_info()):      raisefinally:  if exc:    exit(mgr, None, None, None)

這個過程有幾個細節:

如果上下文管理器中沒有 __enter()__ 或者 __exit()__ 中的任意一個方法,那麼解譯器會拋出一個 AttributeError 。
在 BLOCK 中發生異常後,如果 __exit()__ 方法返回一個可被看成是 True 的值,那麼這個異常就不會被拋出,後面的代碼會繼續執行。

接下來,用兩種方法來實現上面來實現上面的過程的吧。

實現上下文管理器類

第一種方法是實現一個類,其含有一個執行個體屬性 db 和上下文管理器所需要的方法 __enter()__ 和 __exit()__ 。

class transaction(object):  def __init__(self, db):    self.db = db  def __enter__(self):    self.db.begin()  def __exit__(self, type, value, traceback):    if type is None:      db.commit()    else:      db.rollback()

瞭解 with 的執行過程後,這個實現方式是很容易理解的。下面介紹的實現方式,其原理理解起來要複雜很多。

使用產生器裝飾器

在Python的標準庫中,有一個裝飾器可以通過產生器擷取上下文管理器。使用產生器裝飾器的實現過程如下:

from contextlib import contextmanager@contextmanagerdef transaction(db):  db.begin()  try:    yield db  except:    db.rollback()    raise  else:    db.commit()

第一眼上看去,這種實現方式更為簡單,但是其機制更為複雜。看一下其執行過程吧:

  1. Python解譯器識別到 yield 關鍵字後, def 會建立一個產生器函數替代常規的函數(在類定義之外我喜歡用函數代替方法)。
  2. 裝飾器 contextmanager 被調用並返回一個協助方法,這個協助函數在被調用後會產生一個 GeneratorContextManager 執行個體。最終 with 運算式中的 EXPR 調用的是由 contentmanager 裝飾器返回的協助函數。
  3. with 運算式調用 transaction(db) ,實際上是調用協助函數。協助函數調用產生器函數,產生器函數建立一個產生器。
  4. 協助函數將這個產生器傳遞給 GeneratorContextManager ,並建立一個 GeneratorContextManager 的執行個體對象作為上下文管理器。
  5. with 運算式調用執行個體對象的上下文管理器的 __enter()__ 方法。
  6. __enter()__ 方法中會調用這個產生器的 next() 方法。這時候,產生器方法會執行到 yield db 處停止,並將 db 作為 next() 的傳回值。如果有 as VAR ,那麼它將會被賦值給 VAR 。
  7. with 中的 BLOCK 被執行。
  8. BLOCK 執行結束後,調用上下文管理器的 __exit()__ 方法。 __exit()__ 方法會再次調用產生器的 next() 方法。如果發生 StopIteration 異常,則 pass 。
  9. 如果沒有發生異常產生器方法將會執行 db.commit() ,否則會執行 db.rollback() 。

再次看看上述過程的代碼大致實現:

def contextmanager(func):  def helper(*args, **kwargs):    return GeneratorContextManager(func(*args, **kwargs))  return helperclass GeneratorContextManager(object):  def __init__(self, gen):    self.gen = gen  def __enter__(self):    try:      return self.gen.next()    except StopIteration:      raise RuntimeError("generator didn‘t yield")  def __exit__(self, type, value, traceback):    if type is None:      try:        self.gen.next()      except StopIteration:        pass      else:        raise RuntimeError("generator didn‘t stop")    else:      try:        self.gen.throw(type, value, traceback)        raise RuntimeError("generator didn‘t stop after throw()")      except StopIteration:        return True      except:        if sys.exc_info()[1] is not value:          raise

總結

Python的 with 運算式包含了很多Python特性。花點時間吃透 with 是一件非常值得的事情。

一些其他的例子

鎖機制

@contextmanagerdef locked(lock):  lock.acquired()  try:    yield  finally:    lock.release()

標準輸出重新導向

@contextmanagerdef stdout_redirect(new_stdout):  old_stdout = sys.stdout  sys.stdout = new_stdout  try:    yield  finally:    sys.stdout = old_stdoutwith open("file.txt", "w") as f:  with stdout_redirect(f):    print "hello world"

[Python Study Notes]with的使用

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.