Python 的 with 語句詳解

來源:互聯網
上載者:User
一、簡介

with是從Python 2.5 引入的一個新的文法,更準確的說,是一種內容相關的管理協議,用於簡化try…except…finally的處理流程。with通過__enter__方法初始化,然後在__exit__中做善後以及處理異常。對於一些需要預先設定,事後要清理的一些任務,with提供了一種非常方便的表達。

with的基本文法如下,EXPR是一個任意運算式,VAR是一個單一的變數(可以是tuple),”as VAR”是可選的。
複製代碼 代碼如下:


with EXPR as VAR:
BLOCK


根據PEP 343的解釋,with…as…會被翻譯成以下語句:
複製代碼 代碼如下:


mgr = (EXPR)
exit = type(mgr).__exit__ # Not calling it yet
value = type(mgr).__enter__(mgr)
exc = True
try:
try:
VAR = value # Only if "as VAR" is present
BLOCK
except:
# The exceptional case is handled here
exc = False
if not exit(mgr, *sys.exc_info()):
raise
# The exception is swallowed if exit() returns true
finally:
# The normal and non-local-goto cases are handled here
if exc:
exit(mgr, None, None, None)


為什麼這麼複雜呢?注意finally中的代碼,需要BLOCK被執行後才會執行finally的清理工作,因為當EXPR執行時拋出異常,訪問mgr.exit執行就會報AttributeError的錯誤。


二、實現方式

根據前面對with的翻譯可以看到,被with求值的對象必須有一個__enter__方法和一個__exit__方法。稍微看一個檔案讀取的例子吧,注意在這裡我們要解決2個問題:檔案讀取異常,讀取完畢後關閉檔案控制代碼。用try…except一般會這樣寫:
複製代碼 代碼如下:


f = open('/tmp/tmp.txt')
try:
for line in f.readlines():
print(line)
finally:
f.close()


注意我們這裡沒有處理檔案開啟失敗的IOError,上面的寫法可以正常工作,但是對於每個開啟的檔案,我們都要手動關閉檔案控制代碼。如果要使用with來實現上述功能,需要需要一個代理類:
複製代碼 代碼如下:


class opened(object):

def __init__(self, name):
self.handle = open(name)

def __enter__(self):
return self.handle

def __exit__(self, type, value, trackback):
self.handle.close()

with opened('/tmp/a.txt') as f:
for line in f.readlines():
print(line)


注意我們定了一個名字叫opened的輔助類,並實現了__enter__和__exit__方法,__enter__方法沒有參數,__exit__方法的3個參數,分別代表異常的類型、值、以及堆棧資訊,如果沒有異常,3個入參的值都為None。

如果你不喜歡定義class,還可以用Python標準庫提供的contextlib來實現:
複製代碼 代碼如下:


from contextlib import contextmanager

@contextmanager
def opened(name):
f = open(name)
try:
yield f
finally:
f.close()

with opened('/tmp/a.txt') as f:
for line in f.readlines():
print(line)


使用contextmanager的函數,yield只能返回一個參數,而yield後面是處理清理工作的代碼。在我們讀取檔案的例子中,就是關閉檔案控制代碼。這裡原理上和我們之前實現的類opened是相同的,有興趣的可以參考一下contextmanager的原始碼。

三、應用情境

廢話了這麼多,那麼到底那些情境下該使用with,有沒有一些優秀的例子?當然啦,不然這篇文章意義何在。以下摘自PEP 343。

一個確保代碼執行前加鎖,執行後釋放鎖的模板:
複製代碼 代碼如下:


@contextmanager
def locked(lock):
lock.acquire()
try:
yield
finally:
lock.release()

with locked(myLock):
# Code here executes with myLock held. The lock is
# guaranteed to be released when the block is left (even
# if via return or by an uncaught exception).


資料庫事務的提交和復原:
複製代碼 代碼如下:


@contextmanager
def transaction(db):
db.begin()
try:
yield None
except:
db.rollback()
raise
else:
db.commit()


重新導向stdout:
複製代碼 代碼如下:


@contextmanager
def stdout_redirected(new_stdout):
save_stdout = sys.stdout
sys.stdout = new_stdout
try:
yield None
finally:
sys.stdout = save_stdout

with opened(filename, "w") as f:
with stdout_redirected(f):
print "Hello world"


注意上面的例子不是安全執行緒的,再多線程環境中要小心使用。


四、總結

with是對try…expect…finally文法的一種簡化,並且提供了對於異常非常好的處理方式。在Python有2種方式來實現with文法:class-based和decorator-based,2種方式在原理上是等價的,可以根據具體情境自己選擇。

with最初起源於一種block…as…的文法,但是這種文法被很多人所唾棄,最後誕生了with,關於這段曆史依然可以去參考PEP-343和PEP-340

  • 聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.