with語句還能這麼用,你會嗎?——with語句和上下文管理器

啓程

   with 語句究竟有哪些好處?它有助於簡化一些通用資源管理模式,抽象出其中的功能,將其分解並重用。

  若想充分的使用這個特性,比較好的辦法是查看Python標準庫的示例。內置的 open()函數就是一個很好的例子:

with open('hello.txt', 'w') as f:
	f.write('hello, world!')

我們應該都知道打開文件的時候一般建議使用 with 語句,因爲這樣能確保打開的文件描述符在程序執行離開 with語句 的上下文後自動關閉。本質上來說,上面的代碼示例可轉換成下面這樣:

f = open('hello.txt', 'w')
try:
    f.write('hello, world')
finally:
    f.close()

很明顯,這段代碼比with語句冗長。 注意,當中的try……finally 語句也很重要,只關注其中的邏輯代碼還不夠:

f = open('hello.txt', 'w')
f.write('hello, world')
f.close()

如果在調用f.write()時發生異常,這段代碼不能保證文件最後被關閉,因此程序可能會泄露文件描述符。此時with語句就派上用場了,它能夠簡化資源的獲取和釋放。

threading.Lock類是python標準庫中另一個比較好的示例,它有效地使用了with語句

some_loke = threading.Lock()

#常見版:
some_lock.acquire()
try:
    #執行某些操作.....
finally:
    some_loke.release()

#改進版:
with some_loke:
    #執行某些操作.....

在這兩個例子中,使用with語句都可以抽象出大部分資源處理邏輯。不必每次都顯示地寫一個try……finally語句with語句會自行處理。
with 語句不僅讓處理系統資源的代碼更易讀,而且由於絕對不會忘記清理獲釋放資源,因此還可以避免 bug或資源泄露。

下面進行with語句的剖析,在自定義對象中支持with

   無論是 open()函數threading.Lock類本身,還是它們與with語句一起使用,這些都是沒有什麼特殊之處。只要實現所謂的上下文管理器(context manager),就可以在自定義的類和函數中獲得相同的功能。

對了,那上下文管理器是個什麼鬼?這是一個簡單的協議(或接口),自定義對象需要遵循這個接口來支持with語句。總的來說,如果想將一個對象作爲上下文管理器,需要做的就是向其中添加兩個方法:__enter__和__exit__方法。python將在資源管理週期的適當時間調用這兩種方法。

來看看實際代碼,下面是open()上下文管理器的一個簡單實現:

class ManagedFile:
    def __init__(self, name):
        self.name = name
    def __enter__(self):
        self.file = open(self.name, 'w')
        return self.file
    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.file:
            self.file.close()

其中的ManagedFile類遵循上下文管理器協議,所以與原來的open()例子一樣,也支持with語句

with ManagedFile('hello.txt') as f:
    f.write('hello, world!')
    f.write('bey now')

當執行流程進入with語句上下文時,Python會調用__enter__獲取資源;離開with上下文時,Python會調用__exit__釋放資源。

進階

如果想要管理的“資源”是某種報告生成程序中的文本縮進層次,可以編寫下面這樣的代碼:

with Indenter() as indent:
    indent.print('Hi')
    with indent:
        indent.print('hello')
        with indent:
            indent.print('quanluo')
    indent.print('Hey')

這些語句讀起來有點像用於縮進文本的領域特定語言(DSL)。注意這段代碼多次進入離開相同的文本管理器,以此來更改縮進級別。運行這段代碼會在控制檯中整齊地顯示出下面的內容:

  Hi
    hello
      quanluo
  Hey

那麼如何實現一個上下文管理器來支持這種功能呢?
這是一個不錯的練習,可以從中準確的瞭解上下文管理器的工作方式。因此在查看下面的實現之前,最好先花一些時間嘗試自行實現。

如果你已經準備好查看實現代碼了,那麼下面就是使用基於類的上下文管理器來實現的方法:

class Indenter:
    def __init__(self):
        self.level = 0
    def __enter__(self):
        self.level += 1
        return self
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.level -= 1
    def print(self, text):
        print('  ' * self.level + text)

感覺怎麼樣,和你自己想的有差別嗎?
如果還不錯,希望你現在能熟練地在自己的Python程序中使用上下文管理器with語句
這兩個功能很不錯,可以用來以更加有Python特色和可維護的方式處理資源管理問題。

加深理解

如果你還想再找一個練習來加深理解,可以嘗試實現一個使用time.time()函數來測量代碼塊的執行時間的上下文管理器
示例代碼:

with times():
    count = 0
    for i in range(1, 1000001):
            count += i
    print(count)

運行這段代碼會在命令行中顯示下面的內容:

500000500000
程序運行時間爲:0.15491032600402832s

請自行編寫實現該times類的代碼。

探索

with語句也能用來與裝飾器連用,可以自行在自己Python程序上試試.


注: 如果想要關於實現times類的源代碼請在下方評論區告訴我。我看見了會發給你。


🔗原文鏈接:https://blog.csdn.net/weixin_43347550/article/details/105968879

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章