首頁  >  文章  >  後端開發  >  Python的with語句如何使用

Python的with語句如何使用

王林
王林轉載
2023-05-25 17:22:061962瀏覽

  語句體(with-body):with 語句包裹起來的程式碼區塊,在執行語句體之前會呼叫上下文管理器的 enter() 方法,執行完語句體之後會執行 exit() 方法。

  基本語法與工作原理

  with 語句的語法格式如下:

  清單1. with 語句的語法格式

  with context_expression [as target(s)]:

  with-body

  這裡contextexpression 要傳回一個上下文管理器對象,該物件並不賦值給as 子句中的target(s) ,如果指定了as 子句的話,會將上下文管理器的_enter() 方法的回傳值賦值給target(s)。 target(s) 可以是單一變量,或由「()」括起來的元組(不能是僅由「,」分隔的變數列表,必須加上「()」)。

  Python 對一些內建物件進行改進,加入了對上下文管理器的支持,可以用於 with 語句中,例如可以自動關閉檔案、執行緒鎖的自動取得和釋放等。假設要對一個檔案進行操作,使用with 語句可以有以下程式碼:

  清單2. 使用with 語句操作檔案物件

  with open(r'somefileName' ) as somefile:

  for line in somefile:

  print line

  # ...more code

  這裡這裡使用了with 語句,不管在處理檔案過程中是否發生異常,都能保證with 語句執行完畢後已經關閉了開啟的檔案句柄。如果使用傳統的try/finally 範式,則要使用類似如下程式碼:

  清單3. try/finally 方式操作檔案物件

  somefile = open(r' somefileName')

  try:

  for line in somefile:

  print line

  # ...more code

   finally:

  somefile.close()

  比較起來,使用with 語句可以減少編碼量。已經加入對上下文管理協定支援的還有模組 threading、decimal 等。

  PEP 0343 對 with 語句的實作進行了描述。 with 語句的執行過程類似如下程式碼區塊:

  清單4. with 語句執行程序

  context_manager = context_expression

  exit = type(context_manager) .__exit__

  value = type(context_manager).__enter__(context_manager)

  exc = True # True 表示正常執行,即便有異常也忽略;False 表示重新拋出異常,需要對異常進行處理

  try:

  try:

  target = value # 如果使用了as 子句

  with-body # 執行with-body

#  except: # 執行過程中有異常發生

  exc = False

  # 如果__exit__ 返回True,則異常被忽略;如果返回False,則重新拋出異常

  # 由外層程式碼對例外狀況處理

  if not exit(context_manager, *sys.exc_info()):

  raise

 :

##finally:# fin

##  # 正常退出,或透過statement-body 中的break/continue/return 語句退出

  # 或忽略異常退出

#  if exc:

」 (context_manager, None, None, None)

  # 缺省回傳None,None 在布林上下文中看做是False

  執行context_expression,產生上下文管理器context_manager。

  呼叫上下文管理器的 enter() 方法;如果使用了 as 子句,則將 enter() 方法的傳回值賦值給 as 子句中的 target(s)。

  執行語句體with-body

  不管是否執行過程中是否發生了異常,執行上下文管理器的exit() 方法,exit() 方法負責執行「清理」工作,如釋放資源等。如果執行過程中沒有出現異常,或在語句體中執行了語句break/continue/return,則以None 作為參數呼叫exit(None, None, None) ;如果執行過程中出現異常,則使用sys.excinfo 得到的異常訊息為參數呼叫_exit(exc_type, exc_value, exc_traceback)。

  出現異常時,如果exit(type, value, traceback) 返回False,則會重新拋出異常,讓with 之外的語句邏輯來處理異常,這也是通用做法;如果返回True,則忽略異常,不再對異常進行處理。

  自訂上下文管理器

  開發人員可以自訂支援上下文管理協定的類別。自訂的上下文管理器要實現上下文管理協定所需的enter() 和exit() 兩個方法:

#  contextmanager._enter() :進入上下文管理器的執行階段上下文,在語句執行前調用。 with 語句將該方法的回傳值賦值給 as 子句中的 target,如果指定了 as 子句的話。

  contextmanager._exit(exc_type, exc_value, exc_traceback) :退出與上下文管理器相關的執行時間上下文,傳回一個布林值表示是否對發生的例外進行處理。參數表示造成退出操作的異常,如果退出時沒有發生異常,則3個參數都為None。如果發生異常,則回傳。

  True 表示不處理例外,否則會在登出該方法後重新拋出例外值以由 with 語句以外的程式碼邏輯處理。如果方法內部產生異常,則會取代由 statement-body中語句所產生的異常。要處理異常時,不要顯示重新拋出異常,即無法重新拋出通過參數傳遞進來的異常,只需要將返回值設為 False 就可以了。之後,上下文管理程式碼會偵測是否 exit() 失敗來處理異常。

  以下透過一個簡單的範例來示範如何建立自訂的上下文管理器。請注意,上下文管理器必須同時提供 enter() 和 exit() 方法的定義,缺少任何一個都會導致 AttributeError;with 語句會先檢查是否提供了 exit() 方法,然後檢查是否定義了 enter() 方法。

  假設有一個資源DummyResource,這種資源需要在存取前先分配,使用完後再釋放掉;分配操作可以放到enter() 方法中,釋放操作可以放到exit() 方法中。簡單起見,這裡只透過列印語句來表示目前的操作,並沒有實際的資源分配與釋放。

  清單5. 自訂支援with 語句的物件

  class DummyResource:

  def __init__(self, tag):

  def __init__(self, tag):

  def __init__(self, tag):

  def __init__(self, tag):

  self.tag = tag

  print 'Resource [%s]' % tag

  def __enter__(self):

  print '[Enter %s]: Allocate resource .' % self.tag

  return self # 可以回傳不同的物件

  def __exit__(self, exc_type, exc_value, exc_tb):

#tbh 〕#print」[Exit %#; ]: Free resource.' % self.tag

  if exc_tb is None:

  print '[Exit %s]: Exited without exception.' % self.tag

else:

  print '[Exit %s]: Exited with exception raised.' % self.tag

  return False # 可以省略,缺省的None也是被看做是False

#  DummyResource 中的enter() 回傳的是自身的引用,這個引用可以賦值給as 子句中的target 變數;傳回值的類型可以根據實際需要設定為不同的類型,不必是上下文管理器對象本身。

  exit() 方法中對變數exctb 進行檢測,如果不為None,表示發生了異常,返回False 表示需要由外部程式碼邏輯對異常進行處理;注意到如果沒有發生異常,則缺省的傳回值為None,在布林環境中也是被看做False,但是由於沒有異常發生,_exit() 的三個參數都為None,上下文管理程式碼可以偵測這種情況,做正常處理。

  下面在with 語句中存取DummyResource :

  

清單6. 使用自訂的支援with 語句的物件

  with DummyResource('Normal' ):

  print '[with-body] Run without exceptions.'

  with DummyResource('With-Exception'):

#  print '[with-body] Run with exception.'

  raise Exception

  print '[with-body] Run with exception. Failed to finish statement-body!'

  第1個with 語句的執行結果如下:

  

清單7. with 語句1執行結果

  Resource [Normal]

  [Enter Normal]: Allocate resource.

  [Enter Normal]: Allocate resource.

  [with-body] Run without exceptions.

  [Exit Normal]: Free resource.

  [Exit Normal]: Exited without exception.##     ,正常執行時會先執行完語句體with-body,然後執行exit() 方法釋放資源。

  第2個with 語句的執行結果如下:

  

清單8. with 語句2執行結果

#  Resource [With-Exception]

  [Enter With-Exception]: Allocate resource.

#  [with-body] Run with exception.

  [Exit With-Exception]: Free resource.

##  [Exit With-Exception]: Free resource.

##  」 #  [Exit With-Exception]: Exited with exception raised.

  Traceback (most recent call last):

  File "G:/demo", line 20, in

##  File "G:/demo", line 20, in raise Exception

##  Exception

###  可以看到,with-body 中發生異常時with-body 並沒有執行完,但資源會保證被釋放掉,同時產生的異常由with 語句之外的程式碼邏輯來捕獲處理。 ######  可以自訂情境管理器來對軟體系統中的資源進行管理,例如資料庫連線、共用資源的存取控制等。 Python 線上文件 Writing Context Managers 提供了一個針對資料庫連線進行管理的上下文管理器的簡單範例。 ######  contextlib 模組###

  contextlib 模組提供了3個物件:裝飾器 contextmanager、函數nested和上下文管理器closing。使用這些對象,可以對現有的生成器函數或物件進行包裝,加入對上下文管理協定的支持,避免了專門編寫上下文管理器來支援 with 語句。

  裝飾器contextmanager

  contextmanager 用於對生成器函數進行裝飾,生成器函數被裝飾以後,返回的是一個上下文管理器,其enter() 和exit() 方法由contextmanager 負責提供,而不再是先前的迭代子。被裝飾的生成器函數只能產生一個值,否則會導致異常 RuntimeError;產生的值會賦值給 as 子句中的 target,如果使用了 as 子句的話。下面來看一個簡單的例子。

  清單9.裝飾器 contextmanager 使用範例

  from contextlib import contextmanager

#  @contextmanager

   

##  print '[Allocate resources]'

  print 'Code before yield-statement executes in __enter__'

  yield '*** contextmanager demo ***'

##  yield '*** contextmanager demo ***'

  print 'Code after yield-statement executes in __exit__'

  print '[Free resources]'

  with demo() as value:

  with demo() as value:

## %s' % value

  結果輸出如下:  

清單10. contextmanager 使用範例執行結果

  [Allocate resources]

  [Allocate resources]

  [Allocate resources]

  [Allocate resources]

  [Allocate resources]

  [Allocate resources]

」 #  Code before yield-statement executes in __enter__

  Assigned Value: *** contextmanager demo ***

  Code after yield-statement ***  Code after yield-statement executes in#ex;

  可以看到,生成器函數中yield 之前的語句在enter() 方法中執行,yield 之後的語句在exit() 中執行,而yield 產生的值賦給了as 子句中的value 變數。

  需要注意的是,contextmanager 只是省略了enter() / exit() 的編寫,但並不負責實現資源的「獲取」和「清理」工作;「獲取」操作需要定義在yield 語句之前,「清理」操作需要定義yield 語句之後,這樣with 語句在執行enter() / exit() 方法時會執行這些語句以取得/釋放資源,即生成器函數中需要實現必要的邏輯控制,包括資源訪問出現錯誤時拋出適當的異常。

  

函數 nested

  nested 可以將多個上下文管理器組織在一起,避免使用巢狀 with 語句。

  清單11. nested 語法

  with nested(A(), B(), C()) as (X, Y, Z):

  # with-body code here  類似於:

  

  類似於:

  

清單12. nested 執行過程

  with A() as X:

  with B() as Y:

  with C() as Z:

  # with-body code here

  需要注意的是,發生異常之後,如果某個上下文管理器的exit() 方法對異常處理回傳False,則更外層的上下文管理器不會監控到異常。

  上下文管理器closing

  closing 的實作如下:

  

#清單13. 上下文管理closing 實作

##  class closing(object):

  # help doc here

  def __init__(self, thing):

  __enter__(self):

  return self.thing

[  def __exit__(self, *exc_info):

   thing.close()

]>  thing」。管理器會將包裝的物件賦值給as 子句的target 變量,同時確保開啟的物件在with-body 執行完後會關閉。 closing 上下文管理器包裝起來的物件必須提供 close() 方法的定義,否則執行時會報 AttributeError 錯誤。

  

清單14. 自訂支援closing 的物件

######  class ClosingDemo(object):######  def __init__(self):#########  def __init__(self):##########  def __init__(self):##########  defdef __init__(self):##########  defdef __init__(self):##########  def __init__(self):############## #  self.acquire()######  def acquire(self):######  print 'Acquire resources.'######  def free(self):#####  Clean up any resources acquired.'######  def close(self):######  self.free()#######  with closing(ClosingDemo()):#######  with closing(ClosingDemo()):######## print 'Using resources'######  結果輸出如下:######  ###清單15. 自訂closing 物件的輸出結果#########  Acquire resources.##############  Acquire resources.##############  Acquire resources.#### ##  Using resources######  Clean up any resources acquired.######  closing 適用於提供了close() 實現的對象,例如網路連線、資料庫連線等,也可以在自訂類別時透過介面close() 來執行所需的資源「清理」工作。 ###

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

陳述:
本文轉載於:yisu.com。如有侵權,請聯絡admin@php.cn刪除