首頁  >  文章  >  後端開發  >  Python中的with語句與上下文管理器

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

高洛峰
高洛峰原創
2017-03-01 14:15:401186瀏覽

在Python中作為上下文管理器的物件可以使用with語句,提供上下文管理器的contextlib模組的使用則是Python程式設計中的高級技巧,下面我們就來詳細整理一下Python中的with語句與上下文管理器學習總結:

0、關於上下文管理器
上下文管理器是可以在with語句中使用,擁有__enter__和__exit__方法的物件。

with manager as var:
  do_something(var)

相當於以下情況的簡化:

var = manager.__enter__()
try:
  do_something(var)
finally:
  manager.__exit__()

換言之,PEP 343中定義的上下文管理器協定允許將無聊的try...except...finally結構抽像到一個單獨的類別中,僅僅留下關注的do_something部分。

__enter__方法先被呼叫。它可以傳回賦給var的值。 as部分是可選的:如果它不出現,enter的回傳值簡單地被忽略。
with語句下的程式碼被執行。就像try子句,它們或成功執行到底,或break,continue或return,或是可以拋出異常。無論哪種情況,該區塊結束後,__exit__方法被呼叫。如果拋出異常,則異常訊息傳遞給__exit__,這將在下一章討論。通常情況下,異常可被忽略,就像在finally子句中一樣,並且將在__exit__結束後重新拋出。
比如說我們想確認一個檔案在完成寫入作業之後被立即關閉:

>>> class closing(object):
...  def __init__(self, obj):
...   self.obj = obj
...  def __enter__(self):
...   return self.obj
...  def __exit__(self, *args):
...   self.obj.close()
>>> with closing(open('/tmp/file', 'w')) as f:
...  f.write('the contents\n')

這裡我們確保了當with區塊退出時呼叫了f .close()。因為關閉檔案是非常常見的操作,該支援已經出現在file類別中。它有一個__exit__方法呼叫close,並且本身可作為上下文管理器。

>>> with open('/tmp/file', 'a') as f:
...  f.write('more contents\n')

try...finally常見的用法是釋放資源。各種不同的情況實現相似:在__enter__階段資源被獲得,在__exit__階段釋放,如果拋出異常也被傳遞。如文件操作,往往這是物件使用後的自然操作,內建支援使之方便。每一個版本,Python都在更多的地方提供支援。

1、如何使用上下文管理器:

如何開啟一個文件,並寫入"hello world"

filename="my.txt"
mode="w"
writer=open(filename,mode)
writer.write("hello world")
writer.close()

當發生異常時(如磁碟寫滿),就沒有機會執行第5行。當然,我們可以採用try-finally語句區塊進行包裝:

writer=open(filename,mode)
try:
  writer.write("hello world")
finally:
  writer.close()

#當我們進行複雜的操作時,try-finally語句就會變得醜陋,採用with語句重寫:

with open(filename,mode) as writer:
  writer.write("hello world")

as指涉了從open()函數傳回的內容,並且把它賦給了新值。 with完成了try-finally的任務。

2、自訂上下文管理器

with語句的作用類似try-finally,提供一個上下文機制。要應用with語句的類別,其內部必須提供兩個內建函數__enter__和__exit__。前者在主體程式碼執行前執行,後者在主體程式碼執行後執行。 as後面的變量,是在__enter__函數中傳回的。

class echo():
  def output(self):
    print "hello world"
  def __enter__(self):
    print "enter"
    return self #可以返回任何希望返回的东西
  def __exit__(self,exception_type,value,trackback):
    print "exit"
    if exception_type==ValueError:
      return True
    else:
      return Flase
 
>>>with echo as e:
  e.output()


輸出:

#
enter
hello world
exit

完備的__exit_ _函數如下:

def __exit__(self,exc_type,exc_value,exc_tb)

其中,exc_type:異常類型;exc_value:異常值;exc_tb:異常追蹤訊息

#當__exit__當傳回True時,異常不傳播

3、contextlib模組

contextlib模組的作用是提供更易用的上下文管理器,它是透過Generator實現的。 contextlib中的contextmanager作為裝飾器來提供一個針對函數層級的上下文管理機制,常用框架如下:

from contextlib import contextmanager
@contextmanager
def make_context():
  print 'enter'
  try:
    yield "ok"
  except RuntimeError,err:
    print 'error',err
  finally:
    print 'exit'
    
>>>with make_context() as value:
  print value

   
輸出為:

  enter
  ok
  exit

其中,yield寫入try-finally中是為了確保異常安全(能處理異常)as後的變數的值是由yield傳回。 yield前面的語句可看作程式碼區塊執行前操作,yield之後的操作可以看成在__exit__函數中的操作。

以執行緒鎖定為例:

@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

#4、contextlib.nested:減少巢狀

對於:

with open(filename,mode) as reader:
  with open(filename1,mode1) as writer:
    writer.write(reader.read())

可以透過contextlib.nested簡化:

with contextlib.nested(open(filename,mode),open(filename1,mode1)) as (reader,writer):
  writer.write(reader.read())

在python 2.7及以後,被一種新的語法取代:

with open(filename,mode) as reader,open(filename1,mode1) as writer:
  writer.write(reader.read())

5、contextlib.closing()

#file類別直接支援上下文管理器API,但有些表示開啟句柄的物件並不支持,如urllib.urlopen()傳回的物件。還有一些遺留類,使用close()方法而不支援上下文管理器API。為了確保關閉句柄,需要使用closing()為它建立一個上下文管理器(呼叫類別的close方法)。

import contextlib
class myclass():
  def __init__(self):
    print '__init__'
  def close(self):
    print 'close()'
   
with contextlib.closing(myclass()):
  print 'ok'

   
輸出:

__init__
ok
close()


######### #####更多Python中的with語句與上下文管理器相關文章請關注PHP中文網! ###
陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn