1. 程式人生 > >python學習27(with語句)

python學習27(with語句)

With語句
With介紹:
with是從Python2.5引入的一個新的語法,它是一種上下文管理協議,目的在於從流程圖中把 try,except 和finally 關鍵字和資源分配釋放相關程式碼統統去掉,簡化try….except….finlally的處理流程。with通過__enter__方法初始化,然後在__exit__中做善後以及處理異常。所以使用with處理的物件必須有__enter__()和__exit__()這兩個方法。其中__enter__()方法在語句體(with語句包裹起來的程式碼塊)執行之前進入運
行,__exit__()方法在語句體執行完畢退出後執行。with 語句適用於對資源進行訪問的場合,確保不管使用過程中是否發生異常都會執行必要的“清理”操作,釋放資源,比如檔案使用後自動關閉、執行緒中鎖的自動獲取和釋放等。

With基本語法
With語句的基本語法格式如下:
with expression [as target]:
   with_body
引數說明:
expression:是一個需要執行的表示式;
target:是一個變數或者元組,儲存的是expression表示式執行返回的結果, 
可選引數

With 語句是什麼?
有一些任務,可能事先需要設定,事後做清理工作。對於這種場景,Python的with語句提供了一種非常方便的處理方式。一個很好的例子是檔案處理,你需要獲取一個檔案控制代碼,從檔案中讀取資料,然後關閉檔案控制代碼。
如果不用with語句,程式碼如下:
file = open("/tmp/foo.txt")
data = file.read()file.close()
file.close()
這裡有兩個問題:
一是可能忘記關閉檔案控制代碼; 
二是檔案讀取資料發生異常,沒有進行任何處理。
下面是處理異常的加強版本:
try:
f = open('xxx')
except:
    print 'fail to open'
exit(-1)
try:
do something
except:
do something
finally:
     f.close()
雖然這段程式碼執行良好,但是太冗長了。
這時候就是with一展身手的時候了。除了有更優雅的語法,with還可以很好的處理上下文環境產生的異常。
下面是with版本的程式碼:
with open("/tmp/foo.txt") as file:
    data = file.read()

with的工作原理:
緊跟with後面的語句被求值後,返回物件的__enter__()方法被呼叫,這個方
法的返回值將被賦值給as關鍵字後面的變數;
當with後面的程式碼塊全部被執行完之後,將呼叫前面返回物件的__exit__()方法。

程式碼示例:
class Sample(object):
    def __enter__(self):
        print ("In __enter__()")
        return "Foo"

    def __exit__(self,type,value,trace):
        print ("In __exit__()")

def get_sample():
    return Sample()

with get_sample() as sample:
print ("sample: ",sample)
執行結果:

E:\python>python ten.py
In __enter__()
sample:  Foo
In __exit__()
以上程式碼執行過程
1.__enter__()方法被執行 
2.__enter__()方法返回的值 - 這個例子中是”Foo”,賦值給變數’sample’ 
3.執行程式碼塊,列印變數”sample”的值為 “Foo” 
4.__exit__()方法被呼叫 
with真正強大之處是它可以處理異常。可能你已經注意到Sample類的 __exit__ 方法有三個引數 val, type 和 trace。 這些引數在異常處理中相當有用。
class Sample:
    def __enter__(self):
        return self

    def __exit__(self, type, value, trace):
        print ("type:", type)
        print ("value:", value)
        print ("trace:", trace)

    def do_something(self):
        bar = 1/0
        return bar + 10

with Sample() as sample:
sample.do_something()

Sample()的 __enter__() 方法返回新建立的Sample物件,並賦值給變數sample。

執行結果:

type: <type 'exceptions.ZeroDivisionError'>
value: integer division or modulo by zero
trace: <traceback object at 0x00000000026A5B08>
Traceback (most recent call last):
  File "ten.py", line 16, in <module>
    sample.do_something()
  File "ten.py", line 12, in do_something
    bar = 1/0
ZeroDivisionError: integer division or modulo by zero
實際上,在with後面的程式碼塊丟擲任何異常時,__exit__() 方法被執行。正如例子所示,異常丟擲時,與之關聯的type,value和stack trace傳給 __exit__() 方法,因此丟擲的ZeroDivisionError異常被打印出來了。開發庫時,清理資源,關閉檔案等等操作,都可以放在 __exit__ 方法當中。
上文說了 __exit__ 函式可以進行部分異常的處理,如果我們不在這個函式中處理異常,他會正常丟擲,這時候我們可以這樣寫(python 2.7及以上版本,之前的版本參考使用contextlib.nested這個庫函式):
try:
    with open("e:\\python\\222.txt")  as f:
        do someting
else:
do something about exception    
總之,with-as表示式極大的簡化了每次寫finally的工作,這對保持程式碼的優雅性是有極大幫助的。

如果有多個項,可以這麼寫:
with open("e:\\python\\222.txt") as f1,open("e:\\python\\hu.txt") as f2:
    content1 = f1.read()
    content2 = f2.read()
print content1,content2
自定義with異常

class opened(object):
    def __init__(self, filename):
        self.handle = open(filename)
        print ('Resource: %s'% filename)
    def __enter__(self):
        print ('[Enter %s]: Allocate resource.' % self.handle)
        return self.handle  # 可以返回不同的物件
    def __exit__(self, exc_type, exc_value, exc_trackback):
        print ('[Exit %s]: Free resource.' % self.handle)
        if exc_trackback is None:
            print ('[Exit %s]: Exited without exception.,' % self.handle)
            self.handle.close()
        else:
            print ("error occur!")  #控制代碼洩漏
        #return True
        return False  #會丟擲異常,中斷程式的執行

with opened(r'e:\c.txt') as fp:
    for line in fp.readlines():
        print(line)
    raise TypeError

print ("Dnoe")

Return True的執行結果

➢ opened中的__enter__() 返回的是自身的引用,這個引用可以賦值給 as 子句中的fp變數;
返回值的型別可以根據實際需要設定為不同的型別,不必是上下文管理器物件本身。
➢ __exit__() 方法中對變數exc_trackback進行檢測,如果不為 None,表示發生了異常,如果
沒有發生異常,預設的返回值為 None,由於沒有異常發生,__exit__() 的三個引數都為
None,上下文管理程式碼可以檢測這種情況,做正常處理。__exit__()方法的3個引數,分別代
表異常的型別、值、以及堆疊資訊。
➢ 不管是否執行過程中是否發生了異常,執行上下文管理器的 __exit__() 方法,__exit__() 方法
負責執行“清理”工作,如釋放資源等。如果執行過程中沒有出現異常,或者語句體中執行
了語句 break/continue/return,則以 None 作為引數呼叫 __exit__(None, None, None) ;
如果執行過程中出現異常,則使用 sys.exc_info 得到的異常資訊為引數呼叫
__exit__(exc_type, exc_value, exc_traceback)
➢ 出現異常時,如果 __exit__(type, value, traceback) 返回 False,則會重新丟擲異常,讓
with 之外的語句邏輯來處理異常,這也是通用做法;如果返回 True,則忽略異常,不再對
異常進行處理