你真的懂with as 語句嗎?

2021-10-19 17:17:29 字數 4103 閱讀 6185

示例 1

with open('courses.txt') as f:

for i in f:

print(i.strip())

開啟乙個檔案,然後迴圈做一些事情。但是你知道為什麼會有 with 嗎?我們自己是不是能夠寫出可以作用在 with 關鍵字上的物件呢?

with 語句的目的是簡化 try/finally 模式。這種模式用於保證一段**執行完畢後執行某項操作,即便那段**由於異常、return 語句或sys.exit() 呼叫而中止,也會執行指定的操作。finally 子句中的**通常用於釋放重要的資源,或者還原臨時變更的狀態。

示例1的功能我們可以使用 try/finally 模式實現:

學習python還有乙個很重要的點就是需要乙個python的學習環境,如果有在學習中遇到困難,可以來qq群609616831加入我們一起學習,歡迎零基礎和大佬加入,可分享資源

示例2

try:

f = open('courses.txt')

for i in f:

print(i.strip())

finally:

f.close()

try中的 except 和 else 不是必須的,這裡為了簡單明瞭,我們只用了 finally。

對比兩個示例,我們可以看到示例1相對簡潔,這就是 with 的由來。

其實,語言中的一些特性或者說一些亮眼的特性,必然是有乙個演化的過程的,我們作為後來者和使用者應該多花一些心思去思索其背後的實現過程,相信你會收穫更多。

那麼上下文管理器又是什麼呢?

上下文管理器協議包含 __enter__ 和 __exit__ 兩個方法。with 語句開始執行時,會在上下文管理器物件上呼叫 __enter__ 方法。with 語句執行結束後,會在上下文管理器物件上呼叫 __exit__ 方法,以此扮演 finally 子句的角色。最常見的例子是確保關閉檔案物件(示例1)。

下面我們實現乙個簡單的例子來直觀的感受一下:

class t:

def __enter__(self):

print('t.__enter__')

return '我是__enter__的返回值'

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

print('t.__exit__')

with t() as t:

print(t)

輸出:

t.__enter__

我是__enter__的返回值

t.__exit__

示例3中實現了乙個類t,它的物件包含了__enter__和__exit__方法,有了這兩個方法就可以使用 with 處理該物件。執行 with 後面的表示式t()得到的是上下文管理器物件,通過as字句把物件繫結到了變數t上。

觀察輸出結果,可以看到with塊先呼叫了__enter__方法,在處理完內部邏輯(print(t))之後呼叫了exit方法,而t其實就是__enter__方法的返回值。

當然,這個例子只是為了方便我們理解上下文管理器,下面我們看乙個更有意思的例子:

示例4

obj1 = haha('你手機拿反了')

with obj1 as content:

print('哈哈鏡花緣')

print(content)

print('#### with 執行完畢後,再輸出content: ####')

print(content)

輸出:

緣花鏡哈哈

了反拿機手你

#### with 執行完畢後,在輸出content: ####

你手機拿反了

示例4中,上下文管理器是 haha 類的例項,python 呼叫此例項的 __enter__ 方法,把返回結果繫結到 變數content 上。

列印乙個字串,然後列印 content 變數的值。可以看到列印出的內容都是是反向的。

最後,當 with 塊已經執行完畢。可以看出,__enter__ 方法返回的值——即儲存在 content 變數中的值——是字串 『你手機拿反了』。

輸出不再是反向的了。

haha類的實現:

import sys

class haha:

def __init__(self, word):

self.word = word

def reverse_write(self, text):

self.original_write(text[::-1])

def __enter__(self):

self.original_write = sys.stdout.write

sys.stdout.write = self.reverse_write

return self.word

def __exit__(self, exc_type, exc_value, traceback):

sys.stdout.write = self.original_write

return true

在__enter__方法中,我們接管了標準輸出,將其替換成我們自己編寫的方法reverse_write,reverse_write方法將引數內容反轉。而在__exit__方法中,我們將標準輸出還原。__exit__方法需要返回true。

總之,with之於上下文管理器,就像for之於迭代器一樣。with就是為了方便上下文管理器的使用。

上下文管理器特性在標準庫中有一些應用:

另外,說到上下文管理器就不得不提一下@contextmanager 裝飾器,它能減少建立上下文管理器的樣板**量,因為不用編寫乙個完整的類,定義 __enter__和 __exit__ 方法,而只需實現有乙個 yield 語句的生成器,生成想讓 __enter__ 方法返回的值。

在使用 @contextmanager 裝飾的生成器中,yield 語句的作用是把函式的定義體分成兩部分:

yield 語句前面的所有**在 with 塊開始時(即直譯器呼叫 __enter__ 方法時)執行

yield 語句後面的**在with 塊結束時(即呼叫 __exit__ 方法時)執行。

下面我們用 @contextmanager 裝飾器來實現一下示例4的功能:

示例5

import sys

import contextlib

@contextlib.contextmanager

def woha(n):

original_write = sys.stdout.write

def reverse_write(text):

original_write(text[::-1])

sys.stdout.write = reverse_write

yield n

sys.stdout.write =  original_write

return true

obj1 = woha('你手機拿反了')

with obj1 as content:

print('哈哈鏡花緣')

print(content)

print('#### with 執行完畢後,在輸出content: ####')

print(content)

輸出:

緣花鏡哈哈

了反拿機手你

#### with 執行完畢後,在輸出content: ####

你手機拿反了

這裡我們需要注意的是:**執行到yield時,會產出乙個值,這個值會繫結到 with 語句中 as 子句的變數上。執行 with 塊中的**時,這個函式會在yield這裡暫停。此時,相當於示例4中執行完__enter__方法。而控制權一旦跳出 with 塊(塊內**執行完畢)則繼續執行 yield 語句之後的**。

@contextmanager 裝飾器優雅且實用,把三個不同的 python 特性結合到了一起:函式裝飾器、生成器和 with 語句。

現在,我想你應該能夠解答開篇提到的兩個問題了吧!

你真的懂迴圈嗎

好了今天我來講講什麼是迴圈吧,你又真的懂迴圈嗎?讓我來講講迴圈的細節吧和判斷吧 1 for迴圈樣式 for var i 0 i 5 i 它的條件表示式就是先寫for 在寫內部的條件,在js中宣告變數也是可以不加var直接就可以 for i 0 i 5 i 但這樣寫也有一點不對,因為i時區域性變數最好...

你真的懂程式設計嗎?

還記得自己剛學習程式設計的時候,是在大一學習c語言的時候,那時在學習程式設計的時候,一遇到一些問題就開始在網上或者書上找有沒有現成的程式。找到後就把它們稍作修改從而實現自己想要的功能,以為這就是程式設計。到後面自己學習微控制器程式設計的時候也是這樣,雖然我可以實現功能。但是就是感覺自己其實也沒做多少...

你真的懂素數嗎?

素數 即質數 定義 大於1的自然數中,除了1和此整數自身外,沒法被其他自然數整除的數 即除了1和本身沒有其他因子 首先,我們先來看乙個數與其因子有什麼關係 除了本身的因子 因子 數本身 2 如10 的因子 1 2 5 因為2 5 10 5是10的1 2 如果a因子大於5,則b因子就必須是 1,2 即...