首頁  >  文章  >  後端開發  >  Python中的with關鍵字使用詳解

Python中的with關鍵字使用詳解

高洛峰
高洛峰原創
2017-03-28 15:21:121590瀏覽

這篇文章主要介紹了Python 中的with關鍵字使用詳解的相關資料,在Python中,with關鍵字是一個替你管理實現上下文協議對象的好東西,需要的朋友可以參考下

">

在Python 2.5 中, with 關鍵字被加入。它將常用的try ... except ... finally ... 模式很方便的被重複使用。過程中發生了一個異常,那麼在這個異常被拋出前,程式會先將被開啟的檔案關閉。的時候,常常會用類似這樣的程式碼:

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

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

db.begin()
try:
  # do some actions
except:
  db.rollback()
  raise
finally:
  db.commit()

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

表達式

,其結構是這樣的:

with transaction(db):
  # do some actions

#其中: EXPR 可以是任意表達式;as VAR 是可選的。

計算EXPR ,並取得一個上下文管理器。

#呼叫上下文管理器的enter() 方法。

執行BLOCK 中的運算式。異常導致程式退出,那麼異常的type 、 value 和traceback (即sys.exc_info()的回傳值)將會作為參數傳遞給exit() 方法。

#將這個過程用程式碼表示,是這樣的:

with EXPR as VAR:
  BLOCK
這個過程有幾個細節:
  1. 如果上下文管理器中沒有enter() 或exit() 中的任意一個方法,那麼解釋器會拋出一個AttributeError 。

    在 BLOCK 中發生異常後,如果 exit() 方法回傳一個可被看成是 True 的值,那麼這個例外就不會被拋出,後面的程式碼就會繼續執行。

  2. 接下來,用兩種方法來實現上面來實現上面的過程的吧。
  3. 實作上下文管理器類別

  4. 第一種方法是實作一個類,其含有一個實例
  5. 屬性

    db 和上下文管理器所需要的方法enter() 和exit() 。

    mgr = (EXPR)
    exit = type(mgr).exit # 这里没有执行
    value = type(mgr).enter(mgr)
    exc = True
    try:
      try:
        VAR = value # 如果有 as VAR
        BLOCK
      except:
        exc = False
        if not exit(mgr, *sys.exc_info()):
          raise
    finally:
      if exc:
        exit(mgr, None, None, None)
  6. 了解 with 的執行過程後,這個實作方式是很容易理解的。以下介紹的實作方式,其原理理解起來要複雜很多。
  7. 使用
  8. 產生器
  9. 裝飾器

  10. 在Python的標準函式庫中,有一個裝飾器可以透過生成器取得上下文管理器。使用生成器裝飾器的實作過程如下:
  11. 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()

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

Python解釋器辨識到yield 關鍵字後, def 會建立一個生成器

函數

取代常規的函數(在類別定義之外我喜歡用函數代替方法)。


裝飾器 contextmanager 被呼叫並傳回一個幫助方法,這個幫助函數在被呼叫後會產生一個 GeneratorContextManager 實例。最終 with 表達式中的 EXPR 調用的是由 contentmanager 裝飾器傳回的幫助函數。

with 表達式呼叫 transaction(db) ,實際上是呼叫幫助函數。幫助函數呼叫生成器函數,生成器函數建立一個生成器。

幫助函數將這個生成器傳遞給 GeneratorContextManager ,並建立一個 GeneratorContextManager 的實例物件作為上下文管理器。

  • with 表达式调用实例对象的上下文管理器的 enter() 方法。

  • enter() 方法中会调用这个生成器的 next() 方法。这时候,生成器方法会执行到 yield db 处停止,并将 db 作为 next() 的返回值。如果有 as VAR ,那么它将会被赋值给 VAR 。

  • with 中的 BLOCK 被执行。

  • BLOCK 执行结束后,调用上下文管理器的 exit() 方法。 exit() 方法会再次调用生成器的 next() 方法。如果发生 StopIteration 异常,则 pass 。

  • 如果没有发生异常生成器方法将会执行 db.commit() ,否则会执行 db.rollback() 。

  • 再次看看上述过程的代码大致实现:

    def contextmanager(func):
      def helper(*args, **kwargs):
        return GeneratorContextManager(func(*args, **kwargs))
      return helper
    class 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 是一件非常值得的事情。

    一些其他的例子

    锁机制

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

    标准输出重定向

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

    以上是Python中的with關鍵字使用詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

    陳述:
    本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn