99热99这里只有精品6国产,亚洲中文字幕在线天天更新,在线观看亚洲精品国产福利片 ,久久久久综合网

歡迎加入QQ討論群258996829
麥子學(xué)院 頭像
蘋果6袋
6
麥子學(xué)院

Python學(xué)習(xí)之上下文管理器

發(fā)布時(shí)間:2017-02-19 22:26  回復(fù):0  查看:2309   最后回復(fù):2017-02-19 22:26  

本文和大家分享的主要是python中上下文管理器相關(guān)內(nèi)容,一起來看看吧,希望對(duì)大家學(xué)習(xí)python有所幫助。

  所謂上下文

  計(jì)算機(jī)上下文(Context)對(duì)于我而言,一直是一個(gè)很抽象的名詞。就像 形而上 一樣,經(jīng)常聽見有人說,但是無法和現(xiàn)實(shí)認(rèn)知世界相結(jié)合。

  最直觀的上下文,莫過于小學(xué)的語(yǔ)文課,經(jīng)常會(huì)問 聯(lián)系上下文,推測(cè)...,回答...,表明作者... 。文章里的上下文比較好懂,無非就是    。

  直到了解了計(jì)算機(jī)的執(zhí)行狀態(tài),程式的運(yùn)行,才稍微對(duì)計(jì)算機(jī)的上下文(context)有了一定的認(rèn)識(shí),多半還是只可意會(huì),不可言傳。本文所討論的上下文,簡(jiǎn)而言之,就是程式所執(zhí)行的環(huán)境狀態(tài),或者說程式運(yùn)行的情景。

  關(guān)于上下文的定義,我就不在多言,具體通過程式來理解。既然提及上下文,就不可避免的涉及Python中關(guān)于上下文的魔法,即上下文管理器(contextor)。

  資源的創(chuàng)建和釋放場(chǎng)景

  上下文管理器的常用于一些資源的操作,需要在資源的獲取與釋放相關(guān)的操作,一個(gè)典型的例子就是數(shù)據(jù)庫(kù)的連接,查詢,關(guān)閉處理。先看如下一個(gè)例子:

  class Database(object):

  def__init__(self):

  self.connected = False

  defconnect(self):

  self.connected = True

  defclose(self):

  self.connected = False

  defquery(self):

  if self.connected:

  return 'query data'

  else:

  raiseValueError('DB not connected ')

  defhandle_query():

  db = Database()

  db.connect()

  print 'handle --- ', db.query()

  db.close()

  defmain():

  handle_query()

  if __name__ == '__main__':

  main()

  上述的代碼很簡(jiǎn)單,針對(duì) Database 這個(gè)數(shù)據(jù)庫(kù)類,提供了 connect query  close 三種常見的db交互接口。客戶端的代碼中,需要查詢數(shù)據(jù)庫(kù)并處理查詢結(jié)果。當(dāng)然這個(gè)操作之前,需要連接數(shù)據(jù)庫(kù)(db.connect())和操作之后關(guān)閉數(shù)據(jù)庫(kù)連接( db.close())。上述的代碼可以work,可是如果很多地方有類似handle_query的邏輯,連接和關(guān)閉這樣的代碼就得copy很多遍,顯然不是一個(gè)優(yōu)雅的設(shè)計(jì)。

  對(duì)于這樣的場(chǎng)景,在 python黑魔法裝飾器 中有討論如何優(yōu)雅的處理。下面使用裝飾器進(jìn)行改寫如下:

  class Database(object):

  ...

  defdbconn(fn):

  defwrapper(*args, **kwargs):

  db = Database()

  db.connect()

  ret = fn(db, *args, **kwargs)

  db.close()

  return ret

  return wrapper

  @dbconn

  defhandle_query(db=None):

  print 'handle --- ', db.query()

  defmain():

  ...

  編寫一個(gè)dbconn的裝飾器,然后在針對(duì)handle_query進(jìn)行裝飾即可。使用裝飾器,復(fù)用了很多數(shù)據(jù)庫(kù)連接和釋放的代碼邏輯,看起來不錯(cuò)。

  裝飾器解放了生產(chǎn)力。可是,每個(gè)裝飾器都需要事先定義一下db的資源句柄,看起來略丑,不夠優(yōu)雅。

  優(yōu)雅的With as語(yǔ)句

  Python提供了With語(yǔ)句語(yǔ)法,來構(gòu)建對(duì)資源創(chuàng)建與釋放的語(yǔ)法糖。給Database添加兩個(gè)魔法方法:

  class Database(object):

  ...

  def__enter__(self):

  self.connect()

  return self

  def__exit__(self, exc_type, exc_val, exc_tb):

  self.close()

  然后修改handle_query函數(shù)如下:

  defhandle_query():

  withDatabase() as db:

  print 'handle ---', db.query()

  在Database類實(shí)例的時(shí)候,使用with語(yǔ)句。一切正常work。比起裝飾器的版本,雖然多寫了一些字符,但是代碼可讀性變強(qiáng)了。

  上下文管理協(xié)議

  前面初略的提及了上下文,那什么又是上下文管理器呢?與 python黑魔法迭代器 類似,實(shí)現(xiàn)了迭代協(xié)議的函數(shù)/對(duì)象即為迭代器。實(shí)現(xiàn)了上下文協(xié)議的函數(shù)/對(duì)象即為上下文管理器。

  迭代器協(xié)議是實(shí)現(xiàn)了 __iter__ 方法。上下文管理協(xié)議則是 __enter__  __exit__ 。對(duì)于如下代碼結(jié)構(gòu):

  class Contextor:

  def__enter__(self):

  pass

  def__exit__(self, exc_type, exc_val, exc_tb):

  pass

  contextor = Contextor()

  withcontextor [as var]:

  with_body

  Contextor 實(shí)現(xiàn)了 __enter__  __exit__ 這兩個(gè)上下文管理器協(xié)議,當(dāng)Contextor調(diào)用/實(shí)例化的時(shí)候,則創(chuàng)建了上下文管理器 contextor 。類似于實(shí)現(xiàn)迭代器協(xié)議類調(diào)用生成迭代器一樣。

  配合with語(yǔ)句使用的時(shí)候,上下文管理器會(huì)自動(dòng)調(diào)用 __enter__ 方法,然后進(jìn)入運(yùn)行時(shí)上下文環(huán)境,如果有as 從句,返回自身或另一個(gè)與運(yùn)行時(shí)上下文相關(guān)的對(duì)象,值賦值給var。當(dāng)with_body執(zhí)行完畢退出with語(yǔ)句塊或者with_body代碼塊出現(xiàn)異常,則會(huì)自動(dòng)執(zhí)行 __exit__ 方法,并且會(huì)把對(duì)于的異常參數(shù)傳遞進(jìn)來。如果 __exit__ 函數(shù)返回 True 。則with語(yǔ)句代碼塊不會(huì)顯示的拋出異常,終止程序,如果返回None或者False,異常會(huì)被主動(dòng)raise,并終止程序。

  大致對(duì)with語(yǔ)句的執(zhí)行原理總結(jié) Python上下文管理器與with語(yǔ)句 :

  1. 執(zhí)行 contextor 以獲取上下文管理器

  2. 加載上下文管理器的 exit () 方法以備稍后調(diào)用

  3. 調(diào)用上下文管理器的 enter () 方法

  4. 如果有 as var 從句,則將 enter () 方法的返回值賦給 var

  5. 執(zhí)行子代碼塊 with_body

  6. 調(diào)用上下文管理器的 exit () 方法,如果 with_body 的退出是由異常引發(fā)的,那么該異常的 type、value 和 traceback 會(huì)作為參數(shù)傳給 exit (),否則傳三個(gè) None

  7. 如果 with_body 的退出由異常引發(fā),并且 exit () 的返回值等于 False,那么這個(gè)異常將被重新引發(fā)一次;如果 exit () 的返回值等于 True,那么這個(gè)異常就被無視掉,繼續(xù)執(zhí)行后面的代碼

  了解了with語(yǔ)句和上下文管理協(xié)議,或許對(duì)上下文有了一個(gè)更清晰的認(rèn)識(shí)。即代碼或函數(shù)執(zhí)行的時(shí)候,調(diào)用函數(shù)時(shí)候有一個(gè)環(huán)境,在不同的環(huán)境調(diào)用,有時(shí)候效果就不一樣,這些不同的環(huán)境就是上下文。例如數(shù)據(jù)庫(kù)連接之后創(chuàng)建了一個(gè)數(shù)據(jù)庫(kù)交互的上下文,進(jìn)入這個(gè)上下文,就能使用連接進(jìn)行查詢,執(zhí)行完畢關(guān)閉連接退出交互環(huán)境。創(chuàng)建連接和釋放連接都需要有一個(gè)共同的調(diào)用環(huán)境。不同的上下文,通常見于異步的代碼中。

  上下文管理器工具

  通過實(shí)現(xiàn)上下文協(xié)議定義創(chuàng)建上下文管理器很方便,Python為了更優(yōu)雅,還專門提供了一個(gè)模塊用于實(shí)現(xiàn)更函數(shù)式的上下文管理器用法。

  importcontextlib

  @contextlib.contextmanager

  defdatabase():

  db = Database()

  try:

  if not db.connected:

  db.connect()

  yielddb

  exceptExceptionas e:

  db.close()

  defhandle_query():

  withdatabase() as db:

  print 'handle ---', db.query()

  使用contextlib 定義一個(gè)上下文管理器函數(shù),通過with語(yǔ)句,database調(diào)用生成一個(gè)上下文管理器,然后調(diào)用函數(shù)隱式的 __enter__ 方法,并將結(jié)果通yield返回。最后退出上下文環(huán)境的時(shí)候,在excepit代碼塊中執(zhí)行了 __exit__ 方法。當(dāng)然我們可以手動(dòng)模擬上述代碼的執(zhí)行的細(xì)節(jié)。

  In [1]: context = database() # 創(chuàng)建上下文管理器

  In [2]: context

  In [3]: db = context.__enter__() # 進(jìn)入with語(yǔ)句

  In [4]: db # as語(yǔ)句,返回 Database實(shí)例Out[4]:

  In [5]: db.query() Out[5]: 'query data'

  In [6]: db.connectedOut[6]: True

  In [7]: db.__exit__(None, None, None) # 退出with語(yǔ)句

  In [8]: dbOut[8]:

  In [9]: db.connectedOut[9]: False

  上下文管理器的用法

  既然了解了上下文協(xié)議和管理器,當(dāng)然是運(yùn)用到實(shí)踐啦。通常需要切換上下文環(huán)境,往往是在多線程/進(jìn)程這種編程模型。當(dāng)然,單線程異步或者協(xié)程的當(dāng)時(shí),也容易出現(xiàn)函數(shù)的上下文環(huán)境經(jīng)常變動(dòng)。

  異步式的代碼經(jīng)常在定義和運(yùn)行時(shí)存在不同的上下文環(huán)境。此時(shí)就需要針對(duì)異步代碼做上下文包裹的hack??聪旅嬉粋€(gè)例子:

  importtornado.ioloop

  ioloop = tornado.ioloop.IOLoop.instance()

  defcallback():

  print 'run callback'

  raiseValueError('except in callback')

  defasync_task():

  print 'run async task'

  ioloop.add_callback(callback=callback)

  defmain():

  try:

  async_task()

  exceptExceptionas e:

  print 'exception {}'.format(e)

  print 'end'

  main()

  ioloop.start()

  運(yùn)行上述代碼得到如下結(jié)果

  runasynctask

  end

  runcallback

  ERROR:root:Exceptionin callback

  Traceback (mostrecentcalllast):

  ...

  raiseValueError('except in callback')

  ValueError: exceptin callback

  主函數(shù)中main中,定義了異步任務(wù)函數(shù)async_task的調(diào)用。async_task中異常,在except中很容易catch,可是callback中出現(xiàn)的異常,則無法捕捉。原因就是定義的時(shí)候上下文為當(dāng)前的線程執(zhí)行環(huán)境,而使用了tornadoioloop.add_callback方法,注冊(cè)了一個(gè)異步的調(diào)用。當(dāng)callback異步執(zhí)行的時(shí)候,他的上下文已經(jīng)和async_task的上下文不一樣了。因此在main的上下文,無法catch異步中callback的異常。

  下面使用上下文管理器包裝如下:

  class Contextor(object):

  def__enter__(self):

  pass

  def__exit__(self, exc_type, exc_val, exc_tb):

  if all([exc_type, exc_val, exc_tb]):

  print 'handler except'

  print 'exception {}'.format(exc_val)

  return True

  defmain():

  withtornado.stack_context.StackContext(Contextor):

  async_task()

  運(yùn)行main之后的結(jié)果如下:

  runasynctask

  handlerexcept

  runcallback

  handlerexcept

  exceptionexceptin callback

  可見,callback的函數(shù)的異常,在上下文管理器Contextor中被處理了,也就是說callback調(diào)用的時(shí)候,把之前main的上下文保存并傳遞給了callback。當(dāng)然,上述的代碼也可以改寫如下:

  @contextlib.contextmanager

  defcontextor():

  try:

  yield

  exceptExceptionas e:

  print 'handler except'

  print 'exception {}'.format(e)

  finally:

  print 'release'

  defmain():

  withtornado.stack_context.StackContext(contextor):

  async_task()

效果類似。當(dāng)然,也許有人會(huì)對(duì)StackContext這個(gè)tornado的模塊感到迷惑。其實(shí)他恰恰應(yīng)用上下文管理器的魔法的典范。查看StackContext的源碼,實(shí)現(xiàn)非常精秒,非常佩服tornado作者的編碼設(shè)計(jì)能力。

 

來源:伯樂在線

您還未登錄,請(qǐng)先登錄

熱門帖子

最新帖子

?