Python 中的 with 語句與上下文管理器

對象,是 Python 對數據的抽象概念。Python 中的全部數據都是以對象或對象間的關係來實現的。(某種意義上,代碼也是由對象來實現的,這與馮·諾依曼的「stored program computer」模型相一致)。每一個對象都有 id、type、和 value。對象的 id 從建立之時起就再也不改變,你能夠把它想象成對象在內存中的地址。is 運算符就是比較的兩個對象的 id,或者你也能夠經過內建的 id() 函數來直接查看。python

 

正文:

  • with 語句

with 語句是被設計用來簡化「try / finally」語句的。一般的用處在於共享資源的獲取和釋放,好比文件、數據庫和線程資源。它的用法以下:shell

with context_exp [as var]:數據庫

        with_suit函數

with 語句也是複合語句的一種,就像 if、try 同樣,它的後面也有個「:」,而且緊跟一個縮進的代碼塊 with_suit。context_exp 表達式的做用是提供一個上下文管理器(Context Manager),整個 with_suit 代碼塊都是在這個上下文管理器的運行環境下執行的。context_exp 能夠直接是一個上下文管理器的引用,也能夠是一句可執行的表達式,with 語句會自動執行這個表達式以得到上下文管理對象。with 語句的實際執行流程是這樣的:ui

 

  1. 執行 context_exp 以獲取上下文管理器
  2. 加載上下文管理器的 __exit__() 方法以備稍後調用
  3. 調用上下文管理器的 __enter__() 方法
  4. 若是有 as var 從句,則將 __enter__() 方法的返回值賦給 var
  5. 執行子代碼塊 with_suit
  6. 調用上下文管理器的 __exit__() 方法,若是 with_suit 的退出是由異常引起的,那麼該異常的 type、value 和 traceback 會做爲參數傳給 __exit__(),不然傳三個 None
  7. 若是 with_suit 的退出由異常引起,而且 __exit__() 的返回值等於 False,那麼這個異常將被從新引起一次;若是 __exit__() 的返回值等於 True,那麼這個異常就被無視掉,繼續執行後面的代碼

 

即,能夠把 __exit__() 方法當作是「try / finally」的 finally,它老是會被自動調用。Python 裏已經有了一些支持上下文管理協議的對象,好比文件對象,在使用 with 語句處理文件對象時,能夠再也不關心「打開的文件必須記得要關閉」這個問題了:線程

>>> with open('test.py') as f:
	print(f.readline())

	
#!/usr/bin/env python

>>> f.readline()
Traceback (most recent call last):
  File "<pyshell#3>", line 1, in <module>
    f.readline()
ValueError: I/O operation on closed file.

能夠看到在 with 語句完成後,f 已經自動關閉了,這個過程就是在 f 的__exit__() 方法裏完成的。而後下面再來詳細介紹一下上下文管理器:設計

 

  • 內建類型:上下文管理器類型(Context Manager Types)

Python 使用上下文管理協議定義了一種運行時上下文環境(runtime context)。上下文管理協議由包含了一對方法的上下文管理對象實現:它會在子代碼塊運行前進入一個運行時上下文,並在代碼塊結束後退出該上下文。咱們通常使用 with 語句來調用上下文管理對象,這樣代碼清晰度較好。code

上下文管理器的兩個方法:

contextmanager.__enter__()

    • 本方法進入運行時上下文環境,並返回自身或另外一個與運行時上下文相關的對象。返回值會賦給 as 從句後面的變量,as 從句是可選的
    • 舉一個返回自身的栗子好比文件對象。由於文件對象自身就是一個上下文管理器 ,因此「open()」語句返回的文件對象既被 with 獲取,也會經由本方法傳給 as。由下例可見,文件對象內就包含了上下文管理器的兩個方法:
>>> with open('test.py') as f:
	'__enter__' in dir(f)
	'__exit__' in dir(f)

	
True
True
    • 返回一個相關對象的例子好比 decimal.localcontext() 所返回的對象。這個對象的 __enter__() 將 decimal 的主動上下文(active context)放到了一份原版 decimal 上下文的拷貝中並返回。這使得用戶在改動 with 語句內的上下文環境時不會影響到語句外的部分:
>>> import decimal
>>> with decimal.localcontext() as ctx:
	ctx.prec = 22
	print(decimal.getcontext().prec)

	
22
>>> print(decimal.getcontext().prec)
28
    • 有一種 with 的用法是直接使用「with f=open('test.py'):」這樣的語句,不用 as。這種作法在上面提到的 __enter__() 返回 self 時還好說,但若是返回的是另外一個對象好比第二個例子的時候,就很差了。因此這裏的話仍是推薦一概使用 as 來賦值。

 

contextmanager.__exit__(exc_type,exc_val,exc_tb)

    • 本方法退出當前運行時上下文並返回一個布爾值,該布爾值標明瞭「若是 with_suit 的退出是由異常引起的,該異常是否需要被忽略」。若是 __exit__() 的返回值等於 False,那麼這個異常將被從新引起一次;若是 __exit__() 的返回值等於 True,那麼這個異常就被無視掉,繼續執行後面的代碼
    • 另外當 with_suit 的執行過程當中拋出異常時,本方法會當即執行,而且異常的三個參數也會傳進來。下面咱們自定義一個上下文管理器,並能夠經過一個參數設定是否要忽略異常:
>>> class Ctx(object):
	def __init__(self,ign=None):
		self.ign = ign
	def __enter__(self):
		pass
	def __exit__(self,exc_type,exc_val,exc_tb):
		return self.ign

	
>>> with Ctx(True):
	raise TypeError('Ignored?')

>>> with Ctx(False):
	raise TypeError('Ignored?')

Traceback (most recent call last):
  File "<pyshell#28>", line 2, in <module>
    raise TypeError('Ignored?')
TypeError: Ignored?
    • 若是在本方法中又引起了新的異常,那麼這個新異常將覆蓋掉 with_suit 中的異常
相關文章
相關標籤/搜索