欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Python標(biāo)準(zhǔn)模塊--ContextManager上下文管理器的具體用法

 更新時(shí)間:2017年11月27日 10:19:14   作者:老頑童2007  
本篇文章主要介紹了Python標(biāo)準(zhǔn)模塊--ContextManager的具體用法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧

寫代碼時(shí),我們希望把一些操作放到一個(gè)代碼塊中,這樣在代碼塊中執(zhí)行時(shí)就可以保持在某種運(yùn)行狀態(tài),而當(dāng)離開(kāi)該代碼塊時(shí)就執(zhí)行另一個(gè)操作,結(jié)束當(dāng)前狀態(tài);所以,簡(jiǎn)單來(lái)說(shuō),上下文管理器的目的就是規(guī)定對(duì)象的使用范圍,如果超出范圍就采取“處理”。

這一功能是在Python2.5之后引進(jìn)的,它的優(yōu)勢(shì)在于可以使得你的代碼更具可讀性,且不容易出錯(cuò)。

1 模塊簡(jiǎn)介

在數(shù)年前,Python 2.5 加入了一個(gè)非常特殊的關(guān)鍵字,就是with。with語(yǔ)句允許開(kāi)發(fā)者創(chuàng)建上下文管理器。什么是上下文管理器?上下文管理器就是允許你可以自動(dòng)地開(kāi)始和結(jié)束一些事情。例如,你可能想要打開(kāi)一個(gè)文件,然后寫入一些內(nèi)容,最后再關(guān)閉文件。這或許就是上下文管理器中一個(gè)最經(jīng)典的示例。事實(shí)上,當(dāng)你利用with語(yǔ)句打開(kāi)一個(gè)文件時(shí),Python替你自動(dòng)創(chuàng)建了一個(gè)上下文管理器。

with open("test/test.txt","w") as f_obj:
  f_obj.write("hello")

如果你使用的是Python 2.4,你不得不以一種老的方式來(lái)完成這個(gè)任務(wù)。

f_obj = open("test/test.txt","w")
f_obj.write("hello")
f_obj.close()

上下文管理器背后工作的機(jī)制是使用Python的方法:__enter__和__exit__。讓我們嘗試著去創(chuàng)建我們的上下文管理器,以此來(lái)了解上下文管理器是如何工作的。

2 模塊使用

2.1 創(chuàng)建一個(gè)上下文管理器類

與其繼續(xù)使用Python打開(kāi)文件這個(gè)例子,不如我們創(chuàng)建一個(gè)上下文管理器,這個(gè)上下文管理器將會(huì)創(chuàng)建一個(gè)SQLite數(shù)據(jù)庫(kù)連接,當(dāng)任務(wù)處理完畢,將會(huì)將其關(guān)閉。下面就是一個(gè)簡(jiǎn)單的示例。

import sqlite3

class DataConn:
  def __init__(self,db_name):
    self.db_name = db_name

  def __enter__(self):
    self.conn = sqlite3.connect(self.db_name)
    return self.conn

  def __exit__(self,exc_type,exc_val,exc_tb):
    self.conn.close()
    if exc_val:
      raise

if __name__ == "__main__":
  db = "test/test.db"
  with DataConn(db) as conn:
    cursor = conn.cursor()

在上述代碼中,我們創(chuàng)建了一個(gè)類,獲取到SQLite數(shù)據(jù)庫(kù)文件的路徑。__enter__方法將會(huì)自動(dòng)執(zhí)行,并返回?cái)?shù)據(jù)庫(kù)連接對(duì)象?,F(xiàn)在我們已經(jīng)獲取到數(shù)據(jù)庫(kù)連接對(duì)象,然后我們創(chuàng)建光標(biāo),向數(shù)據(jù)庫(kù)寫入數(shù)據(jù)或者對(duì)數(shù)據(jù)庫(kù)進(jìn)行查詢。當(dāng)我們退出with語(yǔ)句的時(shí)候,它將會(huì)調(diào)用__exit__方法用于執(zhí)行和關(guān)閉這個(gè)連接。

讓我們使用其它的方法來(lái)創(chuàng)建上下文管理器。

2.2 利用contextlib創(chuàng)建一個(gè)上下文管理器

Python 2.5 不僅僅添加了with語(yǔ)句,它也添加了contextlib模塊。這就允許我們使用contextlib的contextmanager函數(shù)作為裝飾器,來(lái)創(chuàng)建一個(gè)上下文管理器。讓我們嘗試著用它來(lái)創(chuàng)建一個(gè)上下文管理器,用于打開(kāi)和關(guān)閉文件。

from contextlib import contextmanager

@contextmanager
def file_open(path):
  try:
    f_obj = open(path,"w")
    yield f_obj
  except OSError:
    print("We had an error!")
  finally:
    print("Closing file")
    f_obj.close()

if __name__ == "__main__":
  with file_open("test/test.txt") as fobj:
    fobj.write("Testing context managers")

在這里,我們從contextlib模塊中引入contextmanager,然后裝飾我們所定義的file_open函數(shù)。這就允許我們使用Python的with語(yǔ)句來(lái)調(diào)用file_open函數(shù)。在函數(shù)中,我們打開(kāi)文件,然后通過(guò)yield,將其傳遞出去,最終主調(diào)函數(shù)可以使用它。

一旦with語(yǔ)句結(jié)束,控制就會(huì)返回給file_open函數(shù),它繼續(xù)執(zhí)行yield語(yǔ)句后面的代碼。這個(gè)最終會(huì)執(zhí)行finally語(yǔ)句--關(guān)閉文件。如果我們?cè)诖蜷_(kāi)文件時(shí)遇到了OSError錯(cuò)誤,它就會(huì)被捕獲,最終finally語(yǔ)句依然會(huì)關(guān)閉文件句柄。

contextlib.closing(thing)

contextlib模塊提供了一些很方便的工具。第一個(gè)工具就是closing類,一旦代碼塊運(yùn)行完畢,它就會(huì)將事件關(guān)閉。Python官方文檔給出了類似于以下的一個(gè)示例,

>>> from contextlib import contextmanager
>>> @contextmanager
... def closing(db):
...   try:
...     yield db.conn()
...   finally:
...     db.close()

在這段代碼中,我們創(chuàng)建了一個(gè)關(guān)閉函數(shù),它被包裹在contextmanager中。這個(gè)與closing類相同。區(qū)別就是,我們可以在with語(yǔ)句中使用closing類本身,而非裝飾器。讓我們看如下的示例,

>>> from contextlib import closing
>>> from urllib.request import urlopen
>>> with closing(urlopen("http://www.google.com")) as webpage:
...   for line in webpage:
...     pass

在這個(gè)示例中,我們?cè)赾losing類中打開(kāi)一個(gè)url網(wǎng)頁(yè)。一旦我們運(yùn)行完畢with語(yǔ)句,指向網(wǎng)頁(yè)的句柄就會(huì)關(guān)閉。

contextlib.suppress(*exceptions)

另一個(gè)工具就是在Python 3.4中加入的suppress類。這個(gè)上下文管理工具背后的理念就是它可以禁止任意數(shù)目的異常。假如我們想忽略FileNotFoundError異常。如果你書寫了如下的上下文管理器,那么它不會(huì)正常運(yùn)行。

>>> with open("1.txt") as fobj:
...   for line in fobj:
...     print(line)
...
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
FileNotFoundError: [Errno 2] No such file or directory: '1.txt'

正如你所看到的,這個(gè)上下文管理器沒(méi)有處理這個(gè)異常,如果你想忽略這個(gè)錯(cuò)誤,你可以按照如下方式來(lái)做,

>>> from contextlib import suppress
>>> with suppress(FileNotFoundError):
...   with open("1.txt") as fobj:
...     for line in fobj:
...       print(line)

在這段代碼中,我們引入suppress,然后將我們要忽略的異常傳遞給它,在這個(gè)例子中,就是FileNotFoundError。如果你想運(yùn)行這段代碼,你將會(huì)注意到,文件不存在時(shí),什么事情都沒(méi)有發(fā)生,也沒(méi)有錯(cuò)誤被拋出。請(qǐng)注意,這個(gè)上下文管理器是可重用的,2.4章節(jié)將會(huì)具體解釋。

contextlib.redirect_stdout/redirect_stderr

contextlib模塊還有一對(duì)用于重定向標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤輸出的工具,分別在Python 3.4 和3.5 中加入。在這些工具被加入之前,如果你想對(duì)標(biāo)準(zhǔn)輸出重定向,你需要按照如下方式操作,

import sys
path = "test/test.txt"

with open(path,"w") as fobj:
  sys.stdout = fobj
  help(sum)

利用contextlib模塊,你可以按照如下方式操作,

from contextlib import redirect_stdout

path = "test/test.txt"

with open(path,"w") as fobj:
  with redirect_stdout(fobj):
    help(redirect_stdout)

在上面兩個(gè)例子中,我們均是將標(biāo)準(zhǔn)輸出重定向到一個(gè)文件。當(dāng)我們調(diào)用Python的help函數(shù),不是將信息輸出到標(biāo)準(zhǔn)輸出上,而是將信息保存到重定向的文件中。你也可以將標(biāo)準(zhǔn)輸出重定向到緩存或者從用接口如Tkinter或wxPython中獲取的文件控制類型上。

2.3 ExitStack

ExitStack是一個(gè)上下文管理器,允許你很容易地與其它上下文管理結(jié)合或者清除。這個(gè)咋聽(tīng)起來(lái)讓人有些迷糊,我們來(lái)看一個(gè)Python官方文檔的例子,或許會(huì)讓我們更容易理解它。

>>> from contextlib import ExitStack
>>> filenames = ["1.txt","2.txt"]
>>> with ExitStack as stack:
...   file_objects = [stack.enter_context(open(filename)) for filename in filenames]

這段代碼就是在列表中創(chuàng)建一系列的上下文管理器。ExitStack維護(hù)一個(gè)寄存器的棧。當(dāng)我們退出with語(yǔ)句時(shí),文件就會(huì)關(guān)閉,棧就會(huì)按照相反的順序調(diào)用這些上下文管理器。

Python官方文檔中關(guān)于contextlib有很多示例,你可以學(xué)習(xí)到如下的技術(shù)點(diǎn):

  1. 從__enter__方法中捕獲異常
  2. 支持不定數(shù)目的上下文管理器
  3. 替換掉try-finally
  4. 其它

2.4 可重用的上下文管理器

大部分你所創(chuàng)建的上下文管理器僅僅只能在with語(yǔ)句中使用一次,示例如下:

>>> from contextlib import contextmanager
>>> @contextmanager
... def single():
...   print("Yielding")
...   yield
...   print("Exiting context manager")
...
>>> context = single()
>>> with context:
...   pass
...
Yielding
Exiting context manager
>>> with context:
...   pass
...
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
 File "/usr/lib/python3.4/contextlib.py", line 61, in __enter__
  raise RuntimeError("generator didn't yield") from None
RuntimeError: generator didn't yield

在這段代碼中,我們創(chuàng)建了一個(gè)上下文管理器實(shí)例,并嘗試著在Python的with語(yǔ)句中運(yùn)行兩次。當(dāng)?shù)诙芜\(yùn)行時(shí),它拋出了RuntimeError。

但是如果我們想運(yùn)行上下文管理器兩次呢?我們需要使用可重用的上下文管理器。讓我們使用之前所用過(guò)的redirect_stdout這個(gè)上下文管理器作為示例,

>>> from contextlib import redirect_stdout
>>> from io import StringIO
>>> stream = StringIO()
>>> write_to_stream = redirect_stdout(stream)
>>> with write_to_stream:
...   print("Write something to the stream")
...   with write_to_stream:
...     print("Write something else to stream")
...
>>> print(stream.getvalue())
Write something to the stream
Write something else to stream

在這段代碼中,我們創(chuàng)建了一個(gè)上下文管理器,它們均向StringIO(一種內(nèi)存中的文件流)寫入數(shù)據(jù)。這段代碼正常運(yùn)行,而沒(méi)有像之前那樣拋出RuntimeError錯(cuò)誤,原因就是redirect_stdout是可重用的,允許我們可以調(diào)用兩次。當(dāng)然,實(shí)際的例子將會(huì)有更多的函數(shù)調(diào)用,會(huì)更加的復(fù)雜。一定要注意,可重用的上下文管理器不一定是線程安全的。如果你需要在線程中使用它,請(qǐng)先仔細(xì)閱讀Python的文檔。

2.5 總結(jié)

上下文管理器很有趣,也很方便。我經(jīng)常在自動(dòng)測(cè)試中使用它們,例如,打開(kāi)和關(guān)閉對(duì)話?,F(xiàn)在,你應(yīng)該可以使用Python內(nèi)置的工具去創(chuàng)建你的上下文管理器。你還可以繼續(xù)閱讀Python關(guān)于contextlib的文檔,那里有很多本文沒(méi)有覆蓋到的知識(shí)。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

最新評(píng)論