目錄python
文件IO操做能夠對文件對象使用上下文管理,它主要使用with..as..
語法.數據庫
with open('123.txt') as f: print(f)
要想本身寫的類實現上下文管理,那麼須要用到兩個方法__exit__和__enter__.安全
方法 | 意義 |
---|---|
__enter__ | 進入與此對象相關的上下文,若是存放該方法,with語法會把該方法的返回值綁定到as子句中指定的變量上 |
__exit__ | 退出與此對象相關的上下文 |
class Text: def __enter__(self): print('enter------') def __exit__(self, exc_type, exc_val, exc_tb): print('exit-------') with Text() as f : print('進來了') print('出來了') # enter------ # 進來了 # exit------- # 出來了
實例化對象的時候,並不會調用__enter__方法,只有進入with語句體中,纔會調用__enter__方法,而後執行語句體,最後離開with語句塊的時候,再調用__exit__方法.網絡
with能夠開啓一個上下文運行環境,在執行前作一些準備工做,執行後,作一些收尾工做,它並不會開啓一個新的做用域.app
class Text: def __enter__(self): print('enter------') def __exit__(self, exc_type, exc_val, exc_tb): print('exit-------') with Text() as f : raise Exception # Traceback (most recent call last): # enter------ # File "E:/Python - base - code/chapter08面向對象/練習3.py", line 169, in <module> # raise Exception # exit------- # Exception
咱們能夠看到,with語句體中當異常拋出時,__exit__已經能夠執行完畢,因此上下文管理是安全的.dom
class Text: def __enter__(self): print('enter------') def __exit__(self, exc_type, exc_val, exc_tb): print('exit-------') with Text() as f : print(f) # None
這裏之因此是None,是由於,__enter__函數的返回值爲None,因此若是哪些類的實例化屬性或實例自己要在with語句內部使用,能夠在__enter__函數中進行返回.函數
class Text: def __init__(self): self.name = 'daxin' def __enter__(self): print('enter------') return self def __exit__(self, exc_type, exc_val, exc_tb): print('exit-------') with Text() as f: print(f.name) # daxin
方法的參數以下:ui
注意:__exit__函數的返回值很重要,當返回值等效爲True,表示壓制異常(異常不會上報),等效False時,表示不壓制異常(此時異常會上報)spa
class A: def __init__(self): pass def __enter__(self): print('Enter ~~~~~') def __exit__(self, exc_type, exc_val, exc_tb): print('exit ~~~~~') print(exc_type) print(exc_val) print(exc_tb) return True with A(): raise Exception('Test') # Enter ~~~~~ # <class 'Exception'> # Test # <traceback object at 0x000001E4D0A5C808>
由於__exit__方法return 了True,因此異常被壓制,因此不會異常崩潰。code
下面來計算一個函數的運行時間,主要有兩個辦法:
裝飾器版本:
import time import random import datetime import functools def timer(fn): @functools.wraps(fn) # 拷貝屬性信息 def wrapper(*args, **kwargs): start = datetime.datetime.now() res = fn(*args, **kwargs) stop = (datetime.datetime.now() - start).total_seconds() print(stop) return res return wrapper @timer def add(x, y): time.sleep(random.randrange(1, 5)) return x + y add(4,5)
裝飾器版本2:類裝飾器
import time import random import datetime import functools class Timer: def __init__(self,fn): self.fn = fn functools.wraps(fn)(self) # 拷貝用戶函數屬性信息 def __call__(self, *args, **kwargs): start = datetime.datetime.now() res = self.fn(*args,**kwargs) stop = (datetime.datetime.now() - start).total_seconds() print('The fn run time is {}'.format(stop)) return res @Timer def add(x, y): '''from add function''' time.sleep(random.randrange(1, 5)) return x + y print(add(4,5)) print(add.__name__)
上下文管理器方法1:
import time import random import datetime def add(x, y): time.sleep(random.randrange(1, 5)) return x + y class Timer: def __init__(self): self.start = None def __enter__(self): self.start = datetime.datetime.now() def __exit__(self, exc_type, exc_val, exc_tb): self.stop = (datetime.datetime.now() - self.start).total_seconds() print(self.stop) with Timer(): add(1, 3)
上下文管理版本2:將要計算的函數看成參數傳入上下問管理器中
import time import random import datetime def add(x, y): time.sleep(random.randrange(1, 5)) return x + y class Timer: def __init__(self, fn): self.fn = fn self.start = None def __enter__(self): self.start = datetime.datetime.now() return self.fn def __exit__(self, exc_type, exc_val, exc_tb): self.stop = (datetime.datetime.now() - self.start).total_seconds() print(self.stop) with Timer(add) as f: f(1, 3)
根據上下文管理的特性,總結出三個經常使用的場景:
加強功能
:在代碼執行的先後增長代碼,以加強其功能。相似裝飾器的功能。資源管理
:打來的資源須要關閉,例如文件對象、網絡鏈接、數據庫鏈接等。權限驗證
:在執行代碼以前,作權限的驗證,在__enter__中處理。它是一個裝飾器,用於實現上下文管理,它裝飾一個函數,由於函數沒有像類那樣使用__enter__和__exit__來實現,因此使用contextlib.contextmanger可使一個函數變爲上下文管理器,可是對被裝飾的函數有一個要求,必須包含yeild關鍵字,也就是說這個函數必須返回一個生成器,且只有yield一個值。
這個裝飾器接受一個生成器做爲參數
from contextlib import contextmanager @contextmanager def add(x, y): print('hello') yield x + y print('bye bye') print('start') with add(4, 5) as f: print(f) print('end') # start # hello # 9 # bye bye # end
根據打印結果咱們分析:
當咱們傳入參數add(1, [5,]) 時,異常直接是函數異常退出了,並無執行yield後面的相似__enter__方法的語句,怎麼辦呢?可使用try,finally來捕捉
from contextlib import contextmanager @contextmanager def add(x, y): try: print('hello') yield x + y finally: print('bye bye') print('start') with add(1, [5,]) as f: print(f) print('end')
這樣就會打印yield後續語句,雖然會異常退出,但因爲錯誤的參數由用戶自主傳遞,那就讓用戶本身去解決吧。
業務邏輯簡單,可使用函數加contextlib.contextmanager裝飾器實現,業務邏輯複雜的話,可使用類加__enter__和__exit__來解決。
一個對象可以在運行時,像照鏡子同樣,顯示出其類型信息,這種方法叫作反射。換句話是反射能夠在程序運行的同時獲取類型定義的信息,好比經過一個對象,找出它的type、class、attribute或者method等。具備反射能力的函數有:type()、isinstance()、callable()、dir()、getattr()等。
內建函數 | 含義 |
---|---|
getattr(object, name[, default]) | 經過name返回object的屬性值。當屬性不存在,將使用default返回。 若是沒有設置default,則拋出AttributeError異常,name必須爲字符串。 |
setattr(obj, name, value) | obj的屬性存在,則覆蓋,不存在,則新增。 |
hasattr(obj, name) | 判斷obj是否存在屬性,name必須爲字符串,返回值爲bool類型 |
class Person: def __init__(self,name,age): self.name = name self.age = age def talk(self): print("{} is talking".format(self.name)) daxin = Person('daxin',20) if hasattr(daxin,'name'): # 判斷daxin是否含有name屬性 print(getattr(daxin,'name')) # 若是有,經過getattr獲取name屬性 if not hasattr(daxin,'sing'): # 判斷daxin沒有sing方法 setattr(daxin,'sing',lambda self:print("{} is singing".format(self.name))) # 爲實例綁定一個sing方法 daxin.sing() # 實例調用
直接調用時沒法執行,提示缺乏self參數,想一下,咱們定義的方法一般是在類中定義的,在類中咱們指定的self參數,在實例調用時會進行傳遞(由於是實例是綁定在方法上的),而咱們綁定的sing方法是綁定在實例自己上的,因此這種狀況下,是沒法幫咱們傳遞self參數的,因此咱們在函數內部也沒法調用實例的參數。
這種動態增長屬性的方式是運行時改變類或者實例的方式,而裝飾器或者Mixin是在定義時就決定了的,所以反射具備更大的靈活性。
命令分發器實例:
class Dispather: def __init__(self): pass def register(self, name, func): setattr(self, name, func) def run(self): while True: cmd = input('>>>: ').strip() if cmd.lower() == 'quit': break else: getattr(self, cmd.lower())() d = Dispather() d.register('ls',lambda :print('hello world')) d.run()
魔術方法 | 含義 |
---|---|
__getattr__(self, name) | 定義當用戶試圖獲取一個不存在的屬性時的行爲 |
__setattr__(self, name, value) | 定義當一個屬性被設置時的行爲 |
__delattr__(self, name) | 定義當一個屬性被刪除時的行爲 |
class A: def __init__(self): pass def __getattr__(self, item): print('__getattr__') return 'daxin' daxin = A() print(daxin.name) # __getattr__ # daxin
訪問daxin的一個屬性name,若是不存在,最後會調用__getattr方法,它的返回值就是結果。若是沒有這個方法,就會拋出AttributeError異常,表示找不到屬性。
查找屬性的順序爲:instance.__dict --> instance.__class.__dict --> ... --> object的dict,找不到,調用實例的__getattr__
class A: def __init__(self): pass def __getattr__(self, item): print('__getattr__') return 'daxin' def __setattr__(self, key, value): self.key = value # self.key依舊調用self.__setattr__方法 # self.__dict__[key] = value daxin = A() daxin.name = 'daxin' # 調用__setattr__方法 print(daxin.name)
上面的代碼沒法執行,會產生遞歸是爲何呢?
__setattr__()方法,能夠攔截對實例屬性的增長、修改操做,若是要設置生效,須要本身修改操做實例的__dict__屬性。
class Person: def __init__(self,name): self.name = name self.__dict__['a'] = 5 def __getattr__(self, item): print('getattr~~~~~') return getattr(self,item) def __setattr__(self, key, value): print('setattr~~~~~~') self.__dict__[key] = value # setattr(self,key,value) # 不能這樣寫,這樣寫等同於調用對象的__setattr__方法,會產生遞歸 daxin = Person('daxin') print(daxin.name) print(daxin.a)
結果只會輸出1次getattr,由於初始化時,已經爲字典建立了一個key,a,因此當訪問實例屬性a時,因爲__dict__中存在,因此不會被__getattr__捕獲。
setattr本質上也是經過 instance.attribute = value 的方式賦值的。
刪除一個屬性時,觸發__delattr__方法的執行。能夠阻止經過實例來刪除屬性的操做。
class Person: def __init__(self,name): self.name = name self.__dict__['a'] = 5 def __getattr__(self, item): print('getattr~~~~~') return getattr(self,item) def __setattr__(self, key, value): print('setattr~~~~~~') self.__dict__[key] = value def __delattr__(self, item): print('delattr~~~~~~') del self.__dict__[item] # 刪除實例的屬性, 也能夠在這裏啥也不作,提示不能刪除,便可阻止實例的屬性被刪除。 daxin = Person('daxin') print(daxin.name) del daxin.a # 觸發實例的__delattr__方法的執行。
魔術方法 | 含義 |
---|---|
__getattribute__(self, name) | 定義當該類的屬性被訪問時的行爲 |
實例全部的屬性訪問,第一個都會調用__getattribute__方法。
class Person: def __init__(self,name,age): self.name = name self.age = age def __getattr__(self, item): return 'getattr' def __getattribute__(self, item): pass # return 'ABC' # raise AttributeError('ABC') daxin = Person('daxin',20) print(daxin.name)
上面的例子獲得如下結論:
__getattribute__方法中爲了不在該方法中無限遞歸,它的實現應該永遠調用基類的同名方法以訪問須要的任何屬性(object.__getattribute__(self, name))。
實例屬性查找順序:
instance.__getattribute__() --> instance.__dict__ --> instance.__class__.__dict__ --> object.__dict__ --> instance.__getattr__