歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> Python上下文管理器

Python上下文管理器

日期:2017/3/1 9:27:45   编辑:Linux編程

上下文管理器

在使用Python編程中,可以會經常碰到這種情況:有一個特殊的語句塊,在執行這個語句塊之前需要先執行一些准備動作;當語句塊執行完成後,需要繼續執行一些收尾動作。

例如:當需要操作文件或數據庫的時候,首先需要獲取文件句柄或者數據庫連接對象,當執行完相應的操作後,需要執行釋放文件句柄或者關閉數據庫連接的動作。

又如,當多線程程序需要訪問臨界資源的時候,線程首先需要獲取互斥鎖,當執行完成並准備退出臨界區的時候,需要釋放互斥鎖。

對於這些情況,Python中提供了上下文管理器(Context Manager)的概念,可以通過上下文管理器來定義/控制代碼塊執行前的准備動作,以及執行後的收尾動作。

上下文管理協議

那麼在Python中怎麼實現一個上下文管理器呢?這裡,又要提到兩個"魔術方法",__enter__和__exit__,下面就是關於這兩個方法的具體介紹。

  • __enter__(self) Defines what the context manager should do at the beginning of the block created by the with statement. Note that the return value of __enter__ is bound to the target of the with statement, or the name after the as.
  • __exit__(self, exception_type, exception_value, traceback) Defines what the context manager should do after its block has been executed (or terminates). It can be used to handle exceptions, perform cleanup, or do something always done immediately after the action in the block. If the block executes successfully, exception_type, exception_value, and traceback will be None. Otherwise, you can choose to handle the exception or let the user handle it; if you want to handle it, make sure __exit__ returns True after all is said and done. If you don't want the exception to be handled by the context manager, just let it happen.

也就是說,當我們需要創建一個上下文管理器類型的時候,就需要實現__enter__和__exit__方法,這對方法就稱為上下文管理協議(Context Manager Protocol),定義了一種運行時上下文環境。

with語句

在Python中,可以通過with語句來方便的使用上下文管理器,with語句可以在代碼塊運行前進入一個運行時上下文(執行__enter__方法),並在代碼塊結束後退出該上下文(執行__exit__方法)。

with語句的語法如下:

with context_expr [as var]:
    with_suite
  • context_expr是支持上下文管理協議的對象,也就是上下文管理器對象,負責維護上下文環境
  • as var是一個可選部分,通過變量方式保存上下文管理器對象
  • with_suite就是需要放在上下文環境中執行的語句塊

在Python的內置類型中,很多類型都是支持上下文管理協議的,例如file,thread.LockType,threading.Lock等等。這裡我們就以file類型為例,看看with語句的使用。

with語句簡化文件操作

當需要寫一個文件的時候,一般都會通過下面的方式。代碼中使用了try-finally語句塊,即使出現異常,也能保證關閉文件句柄。

logger = open("log.txt", "w")
try:
    logger.write('Hello ')
    logger.write('World')
finally:
    logger.close()

print logger.closed    

其實,Python的內置file類型是支持上下文管理協議的,可以直接通過內建函數dir()來查看file支持的方法和屬性:

>>> print dir(file)
['__class__', '__delattr__', '__doc__', '__enter__', '__exit__', '__format__', '
__getattribute__', '__hash__', '__init__', '__iter__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclass
hook__', 'close', 'closed', 'encoding', 'errors', 'fileno', 'flush', 'isatty', '
mode', 'name', 'newlines', 'next', 'read', 'readinto', 'readline', 'readlines',
'seek', 'softspace', 'tell', 'truncate', 'write', 'writelines', 'xreadlines']
>>>

所以,可以通過with語句來簡化上面的代碼,代碼的效果是一樣的,但是使用with語句的代碼更加的簡潔:

with open("log.txt", "w") as logger:
    logger.write('Hello ')
    logger.write('World')
    
print logger.closed    

自定義上下文管理器

對於自定義的類型,可以通過實現__enter__和__exit__方法來實現上下文管理器。

看下面的代碼,代碼中定義了一個MyTimer類型,這個上下文管理器可以實現代碼塊的計時功能:

import time

class MyTimer(object):
    def __init__(self, verbose = False):
        self.verbose = verbose
        
    def __enter__(self):
        self.start = time.time()
        return self
        
    def __exit__(self, *unused):
        self.end = time.time()
        self.secs = self.end - self.start
        self.msecs = self.secs * 1000
        if self.verbose:
            print "elapsed time: %f ms" %self.msecs

下面結合with語句使用這個上下文管理器:

def fib(n):
    if n in [1, 2]:
        return 1
    else:
        return fib(n-1) + fib(n-2)
        
with MyTimer(True):
    print fib(30)

代碼輸出結果為:

異常處理和__exit__

在使用上下文管理器中,如果代碼塊 (with_suite)產生了異常,__exit__方法將被調用,而__exit__方法又會有不同的異常處理方式。

當__exit__方法退出當前運行時上下文時,會並返回一個布爾值,該布爾值表明了"如果代碼塊 (with_suite)執行中產生了異常,該異常是否須要被忽略"。

1. __exit__返回False,重新拋出(re-raised)異常到上層

修改前面的例子,在MyTimer類型中加入了一個參數"ignoreException"來表示上下文管理器是否會忽略代碼塊 (with_suite)中產生的異常。

import time

class MyTimer(object):
    def __init__(self, verbose = False, ignoreException = False):
        self.verbose = verbose
        self.ignoreException = ignoreException
        
    def __enter__(self):
        self.start = time.time()
        return self
        
    def __exit__(self, *unused):
        self.end = time.time()
        self.secs = self.end - self.start
        self.msecs = self.secs * 1000
        if self.verbose:
            print "elapsed time: %f ms" %self.msecs
        return self.ignoreException
        
try:        
    with MyTimer(True, False):
        raise Exception("Ex4Test")
except Exception, e:
    print "Exception (%s) was caught" %e
else:
    print "No Exception happened"

運行這段代碼,會得到以下結果,由於__exit__方法返回False,所以代碼塊 (with_suite)中的異常會被繼續拋到上層代碼。

2. __exit__返回Ture,代碼塊 (with_suite)中的異常被忽略

將代碼改為__exit__返回為True的情況:

try:        
    with MyTimer(True, True):
        raise Exception("Ex4Test")
except Exception, e:
    print "Exception (%s) was caught" %e
else:
    print "No Exception happened"

運行結果就變成下面的情況,代碼塊 (with_suite)中的異常被忽略了,代碼繼續運行:

一定要小心使用__exit__返回Ture的情況,除非很清楚為什麼這麼做。

3. 通過__exit__函數完整的簽名獲取更多異常信息

對於__exit__函數,它的完整簽名如下,也就是說通過這個函數可以獲得更多異常相關的信息。

  • __exit__(self, exception_type, exception_value, traceback)

繼續修改上面例子中的__exit__函數如下:

def __exit__(self, exception_type, exception_value, traceback):
    self.end = time.time()
    self.secs = self.end - self.start
    self.msecs = self.secs * 1000
    if self.verbose:
        print "elapsed time: %f ms" %self.msecs
        
    print "exception_type: ", exception_type
    print "exception_value: ", exception_value
    print "traceback: ", traceback
    
    return self.ignoreException

這次運行結果中,就顯示出了更多異常相關的信息了:

總結

本文介紹了Python中的上下文管理器,以及如何結合with語句來使用上下文管理器。

總結一下with 語句的執行流程:

  • 執行context_expr 以獲取上下文管理器對象
  • 調用上下文管理器的 __enter__() 方法
    • 如果有 as var 從句,則將 __enter__() 方法的返回值賦給 var
  • 執行代碼塊 with_suite
  • 調用上下文管理器的 __exit__() 方法,如果 with_suite 產生異常,那麼該異常的 type、value 和 traceback 會作為參數傳給 __exit__(),否則傳三個 None
    • 如果 with_suite 產生異常,並且 __exit__() 的返回值等於 False,那麼這個異常將被重新拋出到上層
    • 如果 with_suite 產生異常,兵器 __exit__() 的返回值等於 True,那麼這個異常就被忽略,繼續執行後面的代碼

在很多情況下,with語句可以簡化代碼,並增加代碼的健壯性。

無需操作系統直接運行 Python 代碼 http://www.linuxidc.com/Linux/2015-05/117357.htm

CentOS上源碼安裝Python3.4 http://www.linuxidc.com/Linux/2015-01/111870.htm

《Python核心編程 第二版》.(Wesley J. Chun ).[高清PDF中文版] http://www.linuxidc.com/Linux/2013-06/85425.htm

《Python開發技術詳解》.( 周偉,宗傑).[高清PDF掃描版+隨書視頻+代碼] http://www.linuxidc.com/Linux/2013-11/92693.htm

Python腳本獲取Linux系統信息 http://www.linuxidc.com/Linux/2013-08/88531.htm

在Ubuntu下用Python搭建桌面算法交易研究環境 http://www.linuxidc.com/Linux/2013-11/92534.htm

Python 語言的發展簡史 http://www.linuxidc.com/Linux/2014-09/107206.htm

Python 的詳細介紹:請點這裡
Python 的下載地址:請點這裡

Copyright © Linux教程網 All Rights Reserved