1. 程式人生 > >[Python]_[初級]_[關於with語句的使用方法]

[Python]_[初級]_[關於with語句的使用方法]

場景

1.在很多專案程式碼裡, 經常會遇到語句with的使用, 這個with到底如何使用的呢, 它又會用在哪方面呢?

2.with語句是python裡特殊的語句, 在Java,Object-C,C++裡是沒有的, 它是否是關鍵的語句, 沒有這個語句是不是很多場景就沒有替代方案了?

說明

0.with語句並不是必須的語句, 它的替代方案就是try/finally語句. 只不過with語句需要的物件比較特殊, 需要自帶__enter__exit__實現,和C++的構造和解構函式很像, 在物件生命週期開始和結束時自動分別呼叫構造和解構函式. 它只是一個具備執行流程順序的封裝語句而已, 只是在資源建立銷燬時使用, 起到精簡程式碼的作用.

1.語法

with_stmt ::=  "with" with_item ("," with_item)* ":" suite
with_item ::=  expression ["as" target]

或者

with EXPR as VAR:
    BLOCK

python解析器在內部大概會解析為:

mgr = (EXPR)
exit = type(mgr).__exit__  # Not calling it yet
value = type(mgr).__enter__(mgr)
exc = True
try:
    try:
        VAR = value  # Only if "as VAR" is present
        BLOCK
    except:
        # The exceptional case is handled here
        exc = False
        if not exit(mgr, *sys.exc_info()):
            raise
        # The exception is swallowed if exit() returns true
finally:
    # The normal and non-local-goto cases are handled here
    if exc:
        exit(mgr, None, None, None)

with內部執行順序說明:

  • 1.在with_item裡的上下文表達式執行獲取一個context manager.
  • 2.載入context manager 力的__exit__() , 注意是載入, 不是呼叫.
  • 3.呼叫context manager裡的__enter__函式.
  • 4.如果target或者說有as關鍵字, 那麼__enter__方法的返回值賦值給target.
  • 5.執行suite
  • 6.context manager__exit__被呼叫, 如果有異常, 那麼traceback會傳遞給__exit__, 否則傳遞None

2.with

語句目的就是為了提供一個標準的try/finally使用方式. 注意, 沒有except, 該丟擲的異常還是會丟擲的. 它的重點在context manager上, 滿足contextmanager protocol(協議)的物件稱之為context manager. 這個協議物件必須實現了 __enter__()__exit__()方法.在標準庫裡, files, sockets, 和locks 物件都實現了這個協議.

比如

- file
- thread.LockType
- threading.Lock
- threading.RLock
- threading.Condition
- threading.Semaphore
- threading.BoundedSemaphore

3.open() 的位置在 The Python Standard Library->Built in Functions 裡. 如果開啟失敗, 那麼會丟擲一個 OSError 錯誤. with是捕抓不到這個異常的.

4.自定義contextmanager的方式有兩種, 一種是定義一個類, 並實現__enter____exit__方法; 另外一種是使用@contextmanager來修飾generator函式, 這種比較方便, 只需要定義一個函式即可.

例子

以下例子列舉了contextmanager的兩種建立方式:


import io
from contextlib import contextmanager

class tranaction:
    def __init__(self, obj):
        print ("get a db handle")
        self.obj = obj

    def __enter__(self):
        print ("begin db tranaction")
        return self.obj

    def __exit__(self, *exc_info):
        if exc_info is None:
            print ("rollback db tranaction")
        else:
            print ("commit db tranaction")

@contextmanager
def TranactionDecorator(obj):
    print("begin db tranaction")
    try:
        yield None
    except Exception:
        print("do db rollback")
    else:
        print("commit db tranaction")

if __name__ == '__main__':

    print ("====== tranaction class ======")
    with tranaction(None):
        print ("do insert intot sql")

    print("====== Tranaction Generator1 ======")
    with TranactionDecorator(None):
        print("do insert intot sql-1")
        raise 1

    print("====== Tranaction Generator2 ======")
    with TranactionDecorator(None):
        print("do insert intot sql-2")

輸出

====== tranaction class ======
get a db handle
begin db tranaction
do insert intot sql
commit db tranaction
====== Tranaction Generator1 ======
begin db tranaction
do insert intot sql-1
do db rollback
====== Tranaction Generator2 ======
begin db tranaction
do insert intot sql-2
commit db tranaction

參考

  1. PEP 343 – The “with” Statement

  2. Python document 裡的 8.5. The with statement.

file object
An object exposing a file-oriented API (with methods such as read() or write()) to an underlying
resource. Depending on the way it was created, a file object can mediate access to a real
on-disk file or to another type of storage or communication device (for example standard 
input/output, in-memory buffers, sockets, pipes, etc.). File objects are also called file-like 
objects or streams.

There are actually three categories of file objects: raw binary files, buffered binary files 
and text files. Their interfaces are defined in the io module. The canonical way to 
create a file object is by using the open() function


generator
A function which returns a generator iterator. It looks like a normal function except that it contains yield expressions for producing a series of values usable in a for-loop or that can be retrieved one at a time with the next() function.

Usually refers to a generator function, but may refer to a generator iterator in some contexts. In cases where the intended meaning isn’t clear, using the full terms avoids ambiguity.

generator iterator
An object created by a generator function.

Each yield temporarily suspends processing, remembering the location execution state (including local variables and pending try-statements). When the generator iterator resumes, it picks up where it left off (in contrast to functions which start fresh on every invocation).


常見的contextmanager修飾符實現方式:

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:
               return
           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:
               # only re-raise if it's *not* the exception that was
               # passed to throw(), because __exit__() must not raise
               # an exception unless __exit__() itself failed.  But
               # throw() has to raise the exception to signal
               # propagation, so this fixes the impedance mismatch
               # between the throw() protocol and the __exit__()
               # protocol.
               #
               if sys.exc_info()[1] is not value:
                   raise

def contextmanager(func):
   def helper(*args, **kwds):
       return GeneratorContextManager(func(*args, **kwds))
   return helper