亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

深入解析Python中的上下文管理器

系統 1539 0

1. 上下文管理器是什么?

舉個例子,你在寫Python代碼的時候經常將一系列操作放在一個語句塊中:

(1)當某條件為真 ?C 執行這個語句塊

(2)當某條件為真 ?C 循環執行這個語句塊

有時候我們需要在當程序在語句塊中運行時保持某種狀態,并且在離開語句塊后結束這種狀態。

所以,事實上上下文管理器的任務是 ?C 代碼塊執行前準備,代碼塊執行后收拾。

上下文管理器是在Python2.5加入的功能,它能夠讓你的代碼可讀性更強并且錯誤更少。接下來,讓我們來看看該如何使用。

2. 如何使用上下文管理器?

看代碼是最好的學習方式,來看看我們通常是如何打開一個文件并寫入”Hello World”?

            
filename = 'my_file.txt'
mode = 'w' # Mode that allows to write to the file
writer = open(filename, mode)
writer.write('Hello ')
writer.write('World')
writer.close()

          

1-2行,我們指明文件名以及打開方式(寫入)。

第3行,打開文件,4-5行寫入“Hello world”,第6行關閉文件。

這樣不就行了,為什么還需要上下文管理器?但是我們忽略了一個很小但是很重要的細節:如果我們沒有機會到達第6行關閉文件,那會怎樣?

舉個例子,磁盤已滿,因此我們在第4行嘗試寫入文件時就會拋出異常,而第6行則根本沒有機會執行。

當然,我們可以使用try-finally語句塊來進行包裝:

            
writer = open(filename, mode)
try:
  writer.write('Hello ')
  writer.write('World')
finally:
  writer.close()

          

finally語句塊中的代碼無論try語句塊中發生了什么都會執行。因此可以保證文件一定會關閉。這么做有什么問題么?當然沒有,但當我們進行一些比寫入“Hello world”更復雜的事情時,try-finally語句就會變得丑陋無比。例如我們要打開兩個文件,一個讀一個寫,兩個文件之間進行拷貝操作,那么通過with語句能夠保證兩者能夠同時被關閉。

OK,讓我們把事情分解一下:

(1)首先,創建一個名為“writer”的文件變量。

(2)然后,對writer執行一些操作。

(3)最后,關閉writer。

這樣是不是優雅多了?

            
with open(filename, mode) as writer:
  writer.write('Hello ') 
  writer.write('World')

          

讓我們深入一點,“with”是一個新關鍵詞,并且總是伴隨著上下文管理器出現?!皁pen(filename, mode)”曾經在之前的代碼中出現?!癮s”是另一個關鍵詞,它指代了從“open”函數返回的內容,并且把它賦值給了一個新的變量。“writer”是一個新的變量名。

2-3行,縮進開啟一個新的代碼塊。在這個代碼塊中,我們能夠對writer做任意操作。這樣我們就使用了“open”上下文管理器,它保證我們的代碼既優雅又安全。它出色的完成了try-finally的任務。

open函數既能夠當做一個簡單的函數使用,又能夠作為上下文管理器。這是因為open函數返回了一個文件類型(file type)變量,而這個文件類型實現了我們之前用到的write方法,但是想要作為上下文管理器還必須實現一些特殊的方法,我會在接下來的小節中介紹。

3. 自定義上下文管理器

讓我們來寫一個“open”上下文管理器。

要實現上下文管理器,必須實現兩個方法 ?C 一個負責進入語句塊的準備操作,另一個負責離開語句塊的善后操作。同時,我們需要兩個參數:文件名和打開方式。

Python類包含兩個特殊的方法,分別名為:__enter__以及__exit__(雙下劃線作為前綴及后綴)。

當一個對象被用作上下文管理器時:

(1)__enter__ 方法將在進入代碼塊前被調用。

(2)__exit__ 方法則在離開代碼塊之后被調用(即使在代碼塊中遇到了異常)。

下面是上下文管理器的一個例子,它分別進入和離開代碼塊時進行打印。

            
class PypixContextManagerDemo:
 
  def __enter__(self):
    print 'Entering the block'
 
  def __exit__(self, *unused):
    print 'Exiting the block'
 
with PypixContextManagerDemo():
  print 'In the block'
 
#Output:
#Entering the block
#In the block
#Exiting the block

          

注意一些東西:

(1)沒有傳遞任何參數。
(2)在此沒有使用“as”關鍵詞。
稍后我們將討論__exit__方法的參數設置。
我們如何給一個類傳遞參數?其實在任何類中,都可以使用__init__方法,在此我們將重寫它以接收兩個必要參數(filename, mode)。

當我們進入語句塊時,將會使用open函數,正如第一個例子中那樣。而當我們離開語句塊時,將關閉一切在__enter__函數中打開的東西。

以下是我們的代碼:

            
class PypixOpen:
 
  def __init__(self, filename, mode):
    self.filename = filename
    self.mode = mode
 
  def __enter__(self):
    self.openedFile = open(self.filename, self.mode)
    return self.openedFile
 
  def __exit__(self, *unused):
    self.openedFile.close()
 
with PypixOpen(filename, mode) as writer:
  writer.write("Hello World from our new Context Manager!")

          

來看看有哪些變化:

(1)3-5行,通過__init__接收了兩個參數。

(2)7-9行,打開文件并返回。

(3)12行,當離開語句塊時關閉文件。

(4)14-15行,模仿open使用我們自己的上下文管理器。

除此之外,還有一些需要強調的事情:

4.如何處理異常

我們完全忽視了語句塊內部可能出現的問題。

如果語句塊內部發生了異常,__exit__方法將被調用,而異常將會被重新拋出(re-raised)。當處理文件寫入操作時,大部分時間你肯定不希望隱藏這些異常,所以這是可以的。而對于不希望重新拋出的異常,我們可以讓__exit__方法簡單的返回True來忽略語句塊中發生的所有異常(大部分情況下這都不是明智之舉)。

我們可以在異常發生時了解到更多詳細的信息,完備的__exit__函數簽名應該是這樣的:

            
def __exit__(self, exc_type, exc_val, exc_tb)

          

這樣__exit__函數就能夠拿到關于異常的所有信息(異常類型,異常值以及異常追蹤信息),這些信息將幫助異常處理操作。在這里我將不會詳細討論異常處理該如何寫,以下是一個示例,只負責拋出SyntaxErrors異常。

            
class RaiseOnlyIfSyntaxError:
 
  def __enter__(self):
    pass
 
  def __exit__(self, exc_type, exc_val, exc_tb):
    return SyntaxError != exc_type


          

捕獲異常:
當一個異常在with塊中拋出時,它作為參數傳遞給__exit__。三個參數被使用,和sys.exc_info()返回的相同:類型、值和回溯(traceback)。當沒有異常拋出時,三個參數都是None。上下文管理器可以通過從__exit__返回一個真(True)值來“吞下”異常。例外可以輕易忽略,因為如果__exit__不使用return直接結束,返回None――一個假(False)值,之后在__exit__結束后重新拋出。

捕獲異常的能力創造了有意思的可能性。一個來自單元測試的經典例子――我們想確保一些代碼拋出正確種類的異常:

            
class assert_raises(object):
  # based on pytest and unittest.TestCase
  def __init__(self, type):
    self.type = type
  def __enter__(self):
    pass
  def __exit__(self, type, value, traceback):
    if type is None:
      raise AssertionError('exception expected')
    if issubclass(type, self.type):
      return True # swallow the expected exception
    raise AssertionError('wrong exception type')

with assert_raises(KeyError):
  {}['foo']


          

5. 談一些關于上下文庫(contextlib)的內容

contextlib是一個Python模塊,作用是提供更易用的上下文管理器。

(1)contextlib.closing

假設我們有一個創建數據庫函數,它將返回一個數據庫對象,并且在使用完之后關閉相關資源(數據庫連接會話等)

我們可以像以往那樣處理或是通過上下文管理器:

            
with contextlib.closing(CreateDatabase()) as database:
  database.query()

          

contextlib.closing方法將在語句塊結束后調用數據庫的關閉方法。

(2)contextlib.nested

另一個很cool的特性能夠有效地幫助我們減少嵌套:

假設我們有兩個文件,一個讀一個寫,需要進行拷貝。

以下是不提倡的:

            
with open('toReadFile', 'r') as reader:
  with open('toWriteFile', 'w') as writer:
    writer.writer(reader.read())

          

可以通過contextlib.nested進行簡化:

            
with contextlib.nested(open('fileToRead.txt', 'r'),
            open('fileToWrite.txt', 'w')) as (reader, writer):
  writer.write(reader.read())

          

在Python2.7中這種寫法被一種新語法取代:

            
with open('fileToRead.txt', 'r') as reader, \
    open('fileToWrite.txt', 'w') as writer:
    writer.write(reader.read())
contextlib.contextmanager


          

對于Python高級玩家來說,任何能夠被yield關鍵詞分割成兩部分的函數,都能夠通過裝飾器裝飾的上下文管理器來實現。任何在yield之前的內容都可以看做在代碼塊執行前的操作,而任何yield之后的操作都可以放在exit函數中。

這里我舉一個線程鎖的例子:

鎖機制保證兩段代碼在同時執行時不會互相干擾。例如我們有兩塊并行執行的代碼同時寫一個文件,那我們將得到一個混合兩份輸入的錯誤文件。但如果我們能有一個鎖,任何想要寫文件的代碼都必須首先獲得這個鎖,那么事情就好辦了。如果你想了解更多關于并發編程的內容,請參閱相關文獻。

下面是線程安全寫函數的例子:

            
import threading
 
lock = threading.Lock()
 
def safeWriteToFile(openedFile, content):
  lock.acquire()
  openedFile.write(content)
  lock.release()

          

接下來,讓我們用上下文管理器來實現,回想之前關于yield和contextlib的分析:

            
@contextlib.contextmanager
def loudLock():
  print 'Locking'
  lock.acquire()
  yield
  print 'Releasing'
  lock.release()
 
with loudLock():
  print 'Lock is locked: %s' % lock.locked()
  print 'Doing something that needs locking'
 
#Output:
#Locking
#Lock is locked: True
#Doing something that needs locking
#Releasing

          

特別注意,這不是異常安全(exception safe)的寫法。如果你想保證異常安全,請對yield使用try語句。幸運的是threading。lock已經是一個上下文管理器了,所以我們只需要簡單地:

            
@contextlib.contextmanager
def loudLock():
  print 'Locking'
  with lock:
    yield
  print 'Releasing'

          

因為threading.lock在異常發生時會通過__exit__函數返回False,這將在yield被調用是被重新拋出。這種情況下鎖將被釋放,但對于“print ‘Releasing'”的調用則不會被執行,除非我們重寫try-finally。

如果你希望在上下文管理器中使用“as”關鍵字,那么就用yield返回你需要的值,它將通過as關鍵字賦值給新的變量。下面我們就仔細來講一下。

6.使用生成器定義上下文管理器
當討論生成器時,據說我們相比實現為類的迭代器更傾向于生成器,因為它們更短小方便,狀態被局部保存而非實例和變量中。另一方面,正如雙向通信章節描述的那樣,生成器和它的調用者之間的數據流可以是雙向的。包括異常,可以直接傳遞給生成器。我們想將上下文管理器實現為特殊的生成器函數。事實上,生成器協議被設計成支持這個用例。

            
@contextlib.contextmanager
def some_generator(
            
              ):
  
              
                
  try:
    yield 
                
                  
  finally:
    
                  
                  
                
              
            
          

contextlib.contextmanager裝飾一個生成器并轉換為上下文管理器。生成器必須遵循一些被包裝(wrapper)函數強制執行的法則――最重要的是它至少yield一次。yield之前的部分從__enter__執行,上下文管理器中的代碼塊當生成器停在yield時執行,剩下的在__exit__中執行。如果異常被拋出,解釋器通過__exit__的參數將之傳遞給包裝函數,包裝函數于是在yield語句處拋出異常。通過使用生成器,上下文管理器變得更短小精煉。

讓我們用生成器重寫closing的例子:

            
@contextlib.contextmanager
def closing(obj):
  try:
    yield obj
  finally:
    obj.close()

          

再把assert_raises改寫成生成器:

            
@contextlib.contextmanager
def assert_raises(type):
  try:
    yield
  except type:
    return
  except Exception as value:
    raise AssertionError('wrong exception type')
  else:
    raise AssertionError('exception expected')

          

這里我們用裝飾器將生成函數轉化為上下文管理器!


更多文章、技術交流、商務合作、聯系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!?。?/p>

發表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 深夜在线小视频 | 国内精品久久久久久网站 | 日本香蕉视频在线观看 | 日日干日日插 | 久久亚洲精品人成综合网 | 999精品久久久中文字幕蜜桃 | 天天操夜夜操狠狠操 | 四虎永久在线精品 | 成人黄色小视频 | 国产在线91 | 男人草女人视频 | 国产一级毛片一区二区三区 | 97视频在线观看视频最新 | 国产精品免费视频一区 | 国产精品香蕉 | 亚洲第一区香蕉_国产a | 亚洲精品久久国产小说 | 久久视频在线免费观看 | 日本精品视频在线观看 | 67194在线午夜亚洲 | 欧美成 人激情视频 | 日韩在线 中文字幕 | 婷婷开心激情 | 精品成人一区二区 | 国产1区精品 | 亚洲欧美另类国产 | 国产精品久久久久蜜芽 | 97在线公开视频 | 天天干天天在线 | 欧美jizzhd精品欧美4k | 九九九热在线精品免费全部 | 亚洲国产精品一区二区九九 | 国产成人精品三级91在线影院 | 99热这里只有精品3 99热这里只有精品4 | 中文字幕在线观看一区二区 | 999热在线精品观看全部 | 国产精品久久二区三区色裕 | 亚洲综合国产一区在线 | 中文字幕亚洲一区婷婷 | 99视频国产在线 | 亚洲精品国精品久久99热 |