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

Python 上下文管理器(contextor)

系統 1748 0

最直觀的上下文,莫過于小學的語文課,經常會問 聯系上下文,推測...,回答...,表明作者... 。文章里的上下文比較好懂,無非就是

在了解了計算機的執行狀態,程式的運行,才稍微對計算機的上下文(context)有了一定的認識,多半還是只可意會,不可言傳。本文所討論的上下文,簡而言之,就是程式所執行的環境狀態,或者說程式運行的情景。

提及上下文,就不可避免的涉及Python中關于上下文的魔法,即上下文管理器(contextor)。

資源的創建和釋放場景

上下文管理器的常用于一些資源的操作,需要在資源的獲取與釋放相關的操作,一個典型的例子就是數據庫的連接,查詢,關閉處理。

先看如下一個例子:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

class Database(object):

?

????def __init__(self):

????????self.connected = False

?

????def connect(self):

????????self.connected = True

?

????def close(self):

????????self.connected = False

?

????def query(self):

????????if self.connected:

????????????return 'query data'

????????else:

????????????raise ValueError('DB not connected ')

?

def handle_query():

????db = Database()

????db.connect()

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

????db.close()

?

def main():

????handle_query()

?

if __name__ == '__main__':

????main()

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

對于這樣的場景,在python黑魔法—裝飾器中有討論如何優雅的處理。下面使用裝飾器進行改寫如下:.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

class Database(object):

????...

?

def dbconn(fn):

????def wrapper(*args, **kwargs):

????????db = Database()

????????db.connect()

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

????????db.close()

????????return ret

????return wrapper

?

@dbconn

def handle_query(db=None):

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

?

def main():

????...

編寫一個dbconn的裝飾器,然后在針對handle_query進行裝飾即可。使用裝飾器,復用了很多數據庫連接和釋放的代碼邏輯,看起來不錯。

裝飾器解放了生產力。可是,每個裝飾器都需要事先定義一下db的資源句柄,看起來略丑,不夠優雅。

?

優雅的With as語句

Python提供了With語句語法,來構建對資源創建與釋放的語法糖。給Database添加兩個魔法方法:

1

2

3

4

5

6

7

8

9

10

class Database(object):

?

????...

?

????def __enter__(self):

????????self.connect()

????????return self

?

????def __exit__(self, exc_type, exc_val, exc_tb):

????????self.close()

然后修改handle_query函數如下:

1

2

3

def handle_query():

????with Database() as db:

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

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

?

上下文管理協議

前面初略的提及了上下文,那什么又是上下文管理器呢?與python黑魔法—迭代器類似,實現了迭代協議的函數/對象即為迭代器。實現了上下文協議的函數/對象即為上下文管理器。

迭代器協議是實現了 __iter__ 方法。上下文管理協議則是 _ _enter__ __exit__ 。對于如下代碼結構:

1

2

3

4

5

6

7

8

9

10

11

class Contextor:

????def __enter__(self):

????????pass

?

????def __exit__(self, exc_type, exc_val, exc_tb):

????????pass

?

contextor = Contextor()

?

with contextor [as var]:

????with_body

Contextor 實現了 __enter__ __exit__ 這兩個上下文管理器協議,當Contextor調用/實例化的時候,則創建了上下文管理器 contextor 。類似于實現迭代器協議類調用生成迭代器一樣。

配合with語句使用的時候,上下文管理器會自動調用 __enter__ 方法,然后進入運行時上下文環境,如果有as 從句,返回自身或另一個與運行時上下文相關的對象,值賦值給var。當with_body執行完畢退出with語句塊或者with_body代碼塊出現異常,則會自動執行 __exit__ 方法,并且會把對于的異常參數傳遞進來。如果 __exit__ 函數返回 True 。則with語句代碼塊不會顯示的拋出異常,終止程序,如果返回None或者False,異常會被主動raise,并終止程序。

大致對with語句的執行原理總結Python上下文管理器與with語句:

  1. 執行 contextor 以獲取上下文管理器
  2. 加載上下文管理器的 exit () 方法以備稍后調用
  3. 調用上下文管理器的 enter () 方法
  4. 如果有 as var 從句,則將 enter () 方法的返回值賦給 var
  5. 執行子代碼塊 with_body
  6. 調用上下文管理器的 exit () 方法,如果 with_body 的退出是由異常引發的,那么該異常的 type、value 和 traceback 會作為參數傳給 exit (),否則傳三個 None
  7. 如果 with_body 的退出由異常引發,并且 exit () 的返回值等于 False,那么這個異常將被重新引發一次;如果 exit () 的返回值等于 True,那么這個異常就被無視掉,繼續執行后面的代碼

了解了with語句和上下文管理協議,或許對上下文有了一個更清晰的認識。即代碼或函數執行的時候,調用函數時候有一個環境,在不同的環境調用,有時候效果就不一樣,這些不同的環境就是上下文。例如數據庫連接之后創建了一個數據庫交互的上下文,進入這個上下文,就能使用連接進行查詢,執行完畢關閉連接退出交互環境。創建連接和釋放連接都需要有一個共同的調用環境。不同的上下文,通常見于異步的代碼中。

?

上下文管理器工具

通過實現上下文協議定義創建上下文管理器很方便,Python為了更優雅,還專門提供了一個模塊用于實現更函數式的上下文管理器用法。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

import contextlib

?

@contextlib.contextmanager

def database():

????db = Database()

????try:

????????if not db.connected:

????????????db.connect()

????????yield db

????except Exception as e:

????????db.close()

?

def handle_query():

????with database() as db:

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

使用contextlib 定義一個上下文管理器函數,通過with語句,database調用生成一個上下文管理器,然后調用函數隱式的 __enter__ 方法,并將結果通yield返回。最后退出上下文環境的時候,在except代碼塊中執行了 __exit__ 方法。當然我們可以手動模擬上述代碼的執行的細節。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

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

?

In [2]: context

?

?

In [3]: db = context.__enter__() # 進入with語句

?

In [4]: db???????????????????????????? # as語句,返回 Database實例

Out[4]:

?

In [5]: db.query()??????

Out[5]: 'query data'

?

In [6]: db.connected

Out[6]: True

?

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

?

In [8]: db

Out[8]:

?

In [9]: db.connected

Out[9]: False

?

上下文管理器的用法

既然了解了上下文協議和管理器,當然是運用到實踐啦。通常需要切換上下文環境,往往是在多線程/進程這種編程模型。當然,單線程異步或者協程的當時,也容易出現函數的上下文環境經常變動。

異步式的代碼經常在定義和運行時存在不同的上下文環境。此時就需要針對異步代碼做上下文包裹的hack。看下面一個例子:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

import tornado.ioloop

?

ioloop = tornado.ioloop.IOLoop.instance()

?

?

def callback():

????print 'run callback'

????raise ValueError('except in callback')

?

def async_task():

????print 'run async task'

????ioloop.add_callback(callback=callback)

?

def main():

?

????try:

????????async_task()

????except Exception as e:

????????print 'exception {}'.format(e)

????print 'end'

?

main()

ioloop.start()

?

運行上述代碼得到如下結果

?

run async task

end

run callback

ERROR:root:Exception in callback

Traceback (most recent call last):

??...

????raise ValueError('except in callback')

ValueError: except in callback

主函數中main中,定義了異步任務函數async_task的調用。async_task中異常,在except中很容易catch,可是callback中出現的異常,則無法捕捉。原因就是定義的時候上下文為當前的線程執行環境,而使用了tornado的ioloop.add_callback方法,注冊了一個異步的調用。當callback異步執行的時候,他的上下文已經和async_task的上下文不一樣了。因此在main的上下文,無法catch異步中callback的異常。

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

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

?

def main():

????with tornado.stack_context.StackContext(Contextor):

????????async_task()

?

運行main之后的結果如下:

?

run async task

handler except

run callback

handler except

exception except in callback

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

1

2

3

4

5

6

7

8

9

10

11

12

13

@contextlib.contextmanager

def contextor():

????try:

????????yield

????except Exception as e:

????????print 'handler except'

????????print 'exception {}'.format(e)

????finally:????

????????print 'release'

?

def main():

????with tornado.stack_context.StackContext(contextor):

????????async_task()

效果類似。當然,也許有人會對StackContext這個tornado的模塊感到迷惑。其實他恰恰應用上下文管理器的魔法的典范。查看StackContext的源碼,實現非常精秒,非常佩服tornado作者的編碼設計能力。至于StackContext究竟如何神秘,已經超出了本篇的范圍,將會在介紹tonrado異步上下文管理器中介紹


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

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯系: 360901061

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

【本文對您有幫助就好】

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

發表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 羞羞网页 | 亚洲欧美日韩专区 | 伊人999| 日本免费一二三区 | 中文国产成人精品久久无广告 | 高清一区二区三区 | 国内一区亚洲综合图区欧美 | 成年女人黄小视频 | 成人18网址在线观看 | 91中文字幕在线观看 | 四虎网站1515hh四虎 | 久久久在线 | 久久国产视频网 | 漂亮女大学一级毛片 | 欧美色综合高清免费 | 久草视频在线网 | 天天天做天天天天爱天天想 | 日韩欧美h | 国产精品久久久久久久久鸭 | 天天色色网 | 在线观看亚洲 | 99这里只精品热在线获取 | 寂寞午夜影院 | 四虎www成人影院免费观看 | 五月婷婷婷婷 | 一级片免费视频 | 午夜在线观看免费影院 | 久久思re热9一区二区三区 | 九九精品激情在线视频 | 99热久久国产这里有只有精品 | 日本不卡在线观看免费v | 欧美一级毛片在线看视频 | 久操视频在线免费观看 | 久草在线精品视频 | 久久中文字幕一区二区三区 | 久久免费精品视频在线观看 | 不卡一级毛片免费高清 | 国产精品大片天天看片 | 综合免费一区二区三区 | 国产免费私人影院永久免费 | 黄黄的网站在线观看 |