在Python中,您須要經過打開文件來訪問文件。您可使用 open()
函數來實現。Open 返回一個文件對象,該文件對象具備用於獲取有關已打開文件的信息和對其進行操做的方法和屬性。html
使用 「with」 語句,使代碼簡潔,處理異常也更優雅。python
「with語句經過封裝經常使用的準備工做和清除任務來簡化異常處理。」數據庫
此外,它將自動關閉文件。with 語句提供了一種確保始終使用清理的方法。express
若是沒有 with 語句,咱們將編寫以下內容:函數
file = open("welcome.txt") data = file.read() print(data) file.close() # 文件用完必定要關閉
'with' 語句是一個新的控制流結構,其基本結構爲:ui
with expression [as variable]: with-block
使用 with 打開文件很是簡單:使用open(filename) as file:
url
with open("welcome.txt") as file: # file 作爲對文件對象的引用 data = file.read() # 使用 data 作點啥
在寫入模式下打開output.txt
線程
with open('output.txt', 'w') as file: # 輸出到file file.write('Hi there!')
注意,咱們沒必要編寫 file.close()
。會被自動調用。code
' with '語句簡化了之前使用try...finally
塊來確保執行清除代碼的代碼。在本節中,我將討論一般使用的語句。在下一節中,我將檢查實現細節,並說明如何編寫用於此語句的對象。htm
with 後面的表達式需支持上下文管理協議 (即,__enter__()
和__exit__()
方法)。
with expression [as variable]: with-block
在執行 with-block 以前調用對象的__enter __() 方法,所以能夠運行setup設置代碼。能夠能過 as 把表達式結果綁定到變量 variable(注意這裏不是賦值到變量 variable)。
with 塊的執行完成後,即便該塊引起了異常,該對象的 __exit__()
方法也會被調用,所以能夠運行清理代碼。
要在Python 2.5中啓用該語句,您須要在模塊中添加如下指令:
from __future__ import with_statement
該語句將始終在 Python 2.6 中啓用。
如今,一些標準的 Python 對象支持上下文管理協議,而且能夠與 'with' 語句一塊兒使用。文件對象便是其中之一:
with open('/etc/passwd', 'r') as f: for line in f: print line ... 更多 ...
執行此語句後,即便for循環在代碼塊中途出現異常,f中的文件對象也將自動關閉。
注意: 在這種狀況下,f
是 open()
建立的同一對象 ,由於 file.__enter__()
返回 self
。
threading
模塊的鎖和條件變量也支持 'with' 語句:
lock = threading.Lock() with lock: # 代碼臨界區 ...
該鎖在執行 with 塊以前獲取,並在該塊完成後始終釋放。
decimal
模塊中 的新 localcontext()
函數使保存和還原當前decimal
上下文變得容易,它封裝了計算所需的精度和舍入特徵:
from decimal import Decimal, Context, localcontext # 顯示默認精度: 28 位數字 v = Decimal('578') print v.sqrt() with localcontext(Context(prec=16)): # 本代碼塊中使用16位精度. # 原始上下文將在退出塊後恢復. print(v.sqrt())
在幕後,with
語句至關複雜。大多數人只會在與現有對象一塊兒使用 'with',而且不須要知道這些詳細信息,若是您想讓本身寫的類也支持 with語句,那就須要瞭解上下文管理器了。
上下文管理協議的高級解釋是:
as var
子句,則僅丟棄該值。__exit__(type,value,traceback)
,該異常詳細信息由sys.exc_info()
返回 。該方法的返回值控制是否從新引起異常:任何 False
值都會從新引起異常,True
會抑制異常。一般不多須要抑制異常,由於若是您這樣作,包含 'with' 語句的代碼的做者將永遠不會意識到任何錯誤。__exit__()
方法,此時參數type,value和traceback都是 None
。讓咱們考慮一個例子。我不會提供詳細的代碼,而只會概述支持事務的數據庫所必需的方法。
(對於不熟悉數據庫術語的人:將對數據庫的一組更改分組爲一個事務。能夠提交事務,這意味着將全部更改都寫入數據庫,也能夠回滾,這意味着將全部更改都丟棄並刪除。數據庫未更改。有關更多信息,請參見任何數據庫教科書。)
假設有一個表明數據庫鏈接的對象。咱們的目標是讓用戶編寫以下代碼:
db_connection = DatabaseConnection() with db_connection as cursor: cursor.execute('insert into ...') cursor.execute('delete from ...') # ... more operations ...
若是塊中的代碼完美運行,則應該提交事務;若是有異常,則應回滾事務。這是我假設的DatabaseConnection的基本接口:
class DatabaseConnection: ... def __enter__ (self): # Code to start a new transaction cursor = self.cursor() return cursor
該__enter __()方法是很簡單的,只有到啓動新的事務。對於此應用程序,結果光標對象將是有用的結果,所以該方法將返回它。而後,用戶能夠添加as cursor
到其 with 語句中,以將遊標綁定到變量名。
class DatabaseConnection: # Database interface def cursor (self): "Returns a cursor object and starts a new transaction" def commit (self): "Commits current transaction" def rollback (self): "Rolls back current transaction"
該__exit __()方法有點複雜,該方法必須檢查是否發生異常。若是沒有異常,則提交事務。若是存在異常,則事務將回滾。
在下面的代碼中,執行會從函數的末尾開始,並返回默認值None
。 None
爲假,所以將自動從新引起異常。若是須要,能夠更加明確,並 在標記的位置添加return語句。
class DatabaseConnection: ... def __exit__ (self, type, value, tb): if tb is None: # No exception, so commit self.commit() else: # Exception occurred, so rollback. self.rollback() # return False
contextlib 模塊提供了一些功能和裝飾器,這些功能和裝飾器對於編寫與 'with' 語句一塊兒使用的對象頗有用。
裝飾器稱爲 contextmanager,它使您能夠編寫一個生成器函數,而不用定義一個新類。生成器應剛好產生一個值。直到yield的代碼 將做爲__enter __()方法執行,而且yield的值將是該方法的返回值,該返回值將綁定到' with '語句的as子句中的變量(若是有)。屈服後的代碼將在 __exit __()方法中執行。塊中引起的任何異常都將由yield語句引起。
上一節中的數據庫示例可使用如下裝飾器編寫爲:
from contextlib import contextmanager @contextmanager def db_transaction (connection): cursor = connection.cursor() try: yield cursor except: connection.rollback() raise else: connection.commit() db = DatabaseConnection() with db_transaction(db) as cursor:
該contextlib模塊還具備嵌套(MGR1, MGR2,...)功能結合了一些上下文管理器,因此你不須要寫嵌套「不與 」語句。在此示例中,單個' with '語句既啓動數據庫事務並獲取線程鎖:
lock = threading.Lock() with nested (db_transaction(db), lock) as (cursor, locked):
最後,Closeing(object)函數返回object,以即可以將其綁定到變量,並object.close()
在塊的末尾調用。
import urllib, sys from contextlib import closing with closing(urllib.urlopen('http://bixuebihui.com')) as f: for line in f: sys.stdout.write(line)