Python世界裏的魔術方法(二)

上下文管理

__enter__

__exit__

文件的IO操作可以對文件對象使用上下文管理,使用with…as語法。

with open('test') as f:
    pass

如果希望類也支持上下文管理,則需要定義兩個函數。

class A:
    def __init__(self):
        print('init')
        
    def __enter__(self):
        print('enter')

    def __exit__(self,exc_type,exc_val,exc_tb):
        print('exit')


with A() as a:
    print(a) # a 此時爲None
    
# 輸出:
# init
# enter
# None
# exit

進入with語句塊時,會調用__enter__,退出with語句塊時,調用__exit__。

因此,退出exit函數時,可以做清理工作,比如關閉文件描述符。即使異常退出,也會執行上下文。因此,涉及到資源申請的操作,最好使用上下文管理語句。

注意:先實例化,才能調用enter。因爲enter是實例方法。 只有with語法時,纔會和enter,exit等函數相關,如此纔會開啓上下文。

上下文管理對象

當一個對象同時實現了__enter__和__exit__方法時,它就屬於上下文管理對象。

方法 意義
__enter__ 進入與此對象相關的上下文。如果存在此方法,with語法會把該方法返回值作爲綁定到as子句中指定的變量上
__exit__ 退出與此對象相關的上下文,包含三個參數:異常類型,異常的值,異常的traceback。
class Point:
    def __init__(self):
        print('init')

    def __enter__(self):
        print('enter')

    def __exit__(self,exc_type,exc_val,exc_tb):
        print('exit')


with Point() as f:
    raise Exception('error')  # 即使拋出異常,也會執行exit。因此,上下文管理是安全的。即使sys.exit也是安全的
    print('do sth.')

注意:如果__exit__返回True,會壓制異常。

函數的上下文和類的上下文

  • 函數的上下文,是同一個對象:

    f = open('1.py')
    with f as a:
        print(f == a) # 同一個對象
        print(id(f),id(a))
    
  • 類的上下文,則不同:

    with A() as a:
        print(a) # None 返回__enter__的return值
    
    with A(): # 
        print(A()) # 返回實例
    # a 和A() 是不同對象
    

上下文應用場景

  • 增強功能

    在代碼執行的前後增加代碼,以增強其功能。類似裝飾器的功能。

  • 資源管理

    打開了資源需要關閉,例如文件對象、網絡連接、數據庫連接等。

  • 權限驗證

    在執行代碼之前,做權限驗證。在__enter__中處理。

應用:使用上下文,實現函數計時器

class Timeit:
    def __init__(self,fn):
        self.fn = fn

    def __enter__(self):
        self.start = datetime.datetime.now()
        return self.fn  #

    def __exit__(self, exc_type, exc_val, exc_tb):
        delta = (datetime.datetime.now() - self.start).total_seconds()
        print('{} took {}s'.format(self.fn.__name__,delta))


with Timeit(add) as f:
    print(add(4,6))

contextlib.contextmanager

它是一個裝飾器,裝飾一個函數後可實現上下文管理。

而無需像類一樣實現__enter__和__exit__ 方法。

它對裝飾的函數是有要求的,必須有yield,也就是這個函數必須返回一個生成器,且只有yield一個值。

也就是這個裝飾器接收一個生成器對象作爲參數。

import contextlib

@contextlib.contextmanager
def foo():
    print('enter') # 相當於__enter__()
    yield 5 # yield的值只能有一個,相當於__enter__方法的返回值
    print('exit') # 相當於__exit__()


with foo() as f:
    print(f)

輸出:

enter
5
exit

f接收了yield語句的返回值。

但是很明顯無法保證exit的執行,如果產生了異常就直接退出。因此增加try finally。

import contextlib

@contextlib.contextmanager
def foo():
    print('enter') # 相當於__enter__()
    try:
        yield 5 # yield的值只能有一個,相當於__enter__方法的返回值
    finally:
        print('exit') # 相當於__exit__()


with foo() as f:
    raise Exception()
    print(f)

如此保證了enter和exit的正常執行。

當yield發生處爲生成器函數增加了上下文管理,這是爲函數增加上下文機制的方式:

  • 把yield之前的當做方法執行
  • 把yield之後的當做__exit____方法執行
  • 把yield的值作爲__enter__的返回值

因此,如果業務邏輯簡單可以使用函數加contextlib的裝飾器方式,如果業務複雜,則使用類的方式加__enter__和__exit__方法方便。

反射相關的魔術方法

__getattr__

__setattr__

__delattr__

__getatrribute__

具體可參考《淺談Python中的反射》

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