查看類的魔術方法html
class A: pass dir(A) # 能夠獲得類全部公有成員
輸出結果以下python
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
在Python中,全部以__
雙下劃線包起來的方法,都統稱爲魔術方法。好比最多見的 __init__
。程序員
__new__
: object.__new__(cls)
建立類的方法:構造函數__del__
:刪除類:析構函數__init__
:初始化函數class A: def __new__(cls, *args, **kwargs): print('new') return object.__new__(cls) def __init__(self): print('init') self.x = 3 def __del__(self): print('del') A() # 返回一個類<__main__.A at 0x7f4a84767978> # 輸出 new init a = A() del a # 輸出del
每當實例空間被收回時(在垃圾收集時),__del__
就會自動執行。shell
class Point: def __init__(self, x, y): self.x = x self.y = y def __add__(self, other): return Point(self.x + other.x, self.y + other.y) def __sub__(self, other): return Point(self.x - other.x, self.y - other.y) a = Point(0, 0) b = Point(3, 5) c = a + b c += Point(4, 6) print(c.x, c.y) # 7, 11 p = Point(3, 5) - Point(2, 1) print(p.x, p.y) # 1, 4
類的對象之間能夠進行加減運算,只要類實現了加減運算對應的魔術方法便可。加法的具體實現是__add__
,減法的具體實現是__sub__
。安全
不要過分使用運算符重載閉包
Point.__add__ = lambda self, value: self - value p = Point(3, 5) + Point(4, 6) print(p.x, p.y) # 輸出-1, -1
__add__
的具體實現若是寫成了減法,這種類型的錯誤很是不容易發現,所以若是不是在寫庫給第三方使用的時候,基本用不上運算符重載。app
hash
對某個對象求hash值時, 會調用對象的__hash__
方法,示例代碼以下In [1]: class Point: ...: def __hash__(self): ...: return 1 ...: In [2]: hash(Point()) Out[2]: 1
__hash__
方法必須返回int,不然會拋出TypeErrorIn [1]: class Point: ...: def __hash__(self): ...: return 'aaa' ...: In [2]: hash(Point()) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-5-a919dcea3eae> in <module>() ----> 1 hash(Point()) TypeError: __hash__ method should return an integer
__hash__
方法的對象In [6]: class Point: ...: def __hash__(self): ...: return 1 ...: In [7]: set([Point(), 12]) # 可hash Out[7]: {<__main__.Point at 0x7f19d4073320>, 12} In [8]: Point.__hash__ = None In [9]: set([Point(), 12]) # 不能放在集合裏面,由於不能hash --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-10-25999920b521> in <module>() ----> 1 set([Point(), 12]) TypeError: unhashable type: 'Point'
__hash__
方法的話,這個類的每一個對象,一般具備不一樣的hashIn [1]: class Point: ...: pass ...: In [2]: p1 = Point() In [3]: p2 = Point() In [4]: hash(p1) Out[4]: 8757059543567 In [5]: hash(p2) Out[5]: 8757059543756
__hash__
會和 __eq__
一塊兒使用, 由於解釋器一般同時判斷hash是否相等以及實例是否相等class Point: def __init__(self, x, y): self.x = x self.y = y def __hash__(self): return hash('{}:{}'.format(self.x, self.y)) def __eq__(self, other): return self.x == other.x and self.y == other.y p1 = Point(3, 5) p2 = Point(3, 5) set([p1, p2]) # 返回 {<__main__.Point at 0x7f286092d588>} hash(p1) == hash(p2) # 返回True p1 == p2 # 返回True
當對象實現了__len__
方法時,可使用內置方法len
求對象的長度, __len__
方法必須返回非負整數ssh
lst = [1, 2, 3] len(lst) # 返回3 lst.__len__() # 返回3
所以內置函數和__len__
方法的效果相同。ide
class Sized: def __len__(self): return 10 len(Sized()) # 返回10
__bool__
方法時, bool(o)
返回值爲o.__bool__()
class F: def __bool__(self): return False bool(F()) # 返回False class T: def __bool__(self): return True bool(T()) # 返回True
__bool__
方法時,若是o實現了__len__
方法, bool(o)
返回值爲 len(o) != 0
class L: def __len__(self): return 3 bool(L()) # 返回True class Q: def __len__(self): return 0 bool(Q()) # 返回False
__bool__
方法,也沒有實現 __len__
方法的時候, bool(o)
返回值爲True
class Boolean: pass bool(Boolean()) # 返回True
__bool__
優先級比__len__
更高class Sized: def __init__(self, size): self.size = size def __len__(self): return self.size def __bool__(self): return self.size == 0 bool(Sized(0)) # 返回True bool(Sized(10)) # 返回False
__bool__
方法必須返回bool類型class B: def __bool__(self): return None # 返回非bool類型的值時會出錯,即便返回int型的也會報錯 bool(B()) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-80-4efbb03885fe> in <module>() ----> 1 bool(B()) TypeError: __bool__ should return bool, returned NoneType
__str__
方法,print函數本質是調用對象的__str__
方法,用於給人讀__repr__
方法,repr函數本質是調用對象的__repr__
方法,用於給機器讀class Point: def __init__(self, x, y): self.x = x self.y = y def __str__(self): # 給人來讀 return 'Point<{}, {}>'.format(self.x, self.y) def __repr__(self): # 給機器讀的 return 'Point({}, {})'.format(self.x, self.y) print(Point(3, 5)) # Point<3, 5> print(repr(Point(3, 5))) # Point(3, 5)
repr:返回對象的規範化的字符串表示函數
class Fn: def __call__(self): print('{} called'.format(self)) f = Fn() f() # 輸出 <__main__.Fn object at 0x7fd254367470> called
一個對象,只要實現了__call__
方法, 就能夠經過小括號來來調用, 這一類對象,稱之爲可調用對象
給對象加上函數也就是對__call__
方法加上參數:
class Add: def __call__(self, x, y): return x + y Add()(3, 5) # 返回8,等價於 add =Add() add(3, 5)
可調用對象的應用實例:實現可過時可換出的cache裝飾器
import inspect import datetime from functools import wraps class Cache: def __init__(self, size=128, expire=0): self.size = size self.expire = 0 self.data = {} @staticmethod def make_key(fn, args, kwargs): ret = [] names = set() params = inspect.signature(fn).parameters keys = list(params.keys()) for i, arg in enumerate(args): ret.append((keys[i], arg)) names.add(keys[i]) ret.extend(kwargs.items()) names.update(kwargs.keys()) for k, v in params.items(): if k not in names: ret.append((k, v.default)) ret.sort(key=lambda x: x[0]) return '&'.join(['{}={}'.format(name, arg) for name, arg in ret]) def __call__(self, fn): @wraps(fn) def wrap(*args, **kwargs): key = self.make_key(fn, args, kwargs) now = datetime.datetime.now().timestamp() if key in self.data.keys(): value, timestamp, _ = self.data[key] if expire == 0 or now - timestamp < expire: self.data[key] = (value, timestamp, now) return value else: self.data.pop(key) value = fn(*args, **kwargs) if len(self.data) >= self.size: # 過時清理 if self.expire != 0: expires = set() for k, (_, timestamp, _) in self.data.items(): if now - timestamp >= self.expire: expires.add(k) for k in expires: self.data.pop(k) if len(self.data) >= self.size: # 換出 k = sorted(self.data.items(), key=lambda x: x[1][2])[0][0] self.data.pop(k) self.data[key] = (value, now, now) return value return wrap @Cache() def add(x, y): return x + y add(1, 2) # 返回3
用__call__
來實現可調用對象,和閉包是異曲同工的,一般是爲了封裝一些內部狀態
class Context: def __enter__(self): print('enter context') def __exit__(self, *args, **kwargs): print('exit context')
當一個對象同時實現了__enter__
和__exit__
方法,那麼這個對象就是支持上下文管理的對象。
支持上下文管理的對象可使用如下語句塊進行處理:
with obj: pass
好比
with Context(): print('do somethings') print('out of context') # 輸出 enter context do somethings exit context out of context
因此,with
開啓一個語句塊, 執行這個語句塊以前,會執行 __enter__
方法, 執行這個語句塊以後,會執行__exit__
方法,也就是說在這個語句塊的先後會執行一些操做,所以也叫上下文。
__enter__
和__exit__
也會被執行,因此上下文管理是安全的。with Context(): raise Exception() enter context exit context --------------------------------------------------------------------------- Exception Traceback (most recent call last) <ipython-input-126-c1afee4bfdab> in <module>() 1 with Context(): ----> 2 raise Exception() Exception:
with
塊中主動退出解釋器, __enter__
和__exit__
也能保證執行import sys with Context(): sys.exit() enter context exit context An exception has occurred, use %tb to see the full traceback. SystemExit /home/clg/.pyenv/versions/3.5.2/envs/normal/lib/python3.5/site-packages/IPython/core/interactiveshell.py:2889: UserWarning: To exit: use 'exit', 'quit', or Ctrl-D. warn("To exit: use 'exit', 'quit', or Ctrl-D.", stacklevel=1)
as
子句能夠獲取__enter__
方法的返回值class Context: def __enter__(self): print('enter context') return self # __enter__函數的返回值 def __exit__(self, *args, **kwargs): print('exit context') ctx = Context() with ctx as c: print(id(ctx)) print(id(c)) print(c) # 輸出結果 enter context 140541332713712 140541332713712 <__main__.Context object at 0x7fd2543670f0> exit context
__enter__
方法__enter__
方法的返回值能夠被as字句捕獲到
__enter__
除self以外,不帶任何參數
class Context: def __enter__(self, *args, **kwargs): print('enter context') print(args) print(kwargs) def __exit__(self, *args, **kwargs): print('exit context') # 輸出 enter context () {} exit context
args和kwargs都是空的,所以上下文管理的時候__enter__
函數除self外,不帶任何參數。
__exit__
方法__exit__
的返回值,沒有辦法獲取到,若是with
塊中拋出異常 __exit__
返回False的時候,會向上拋出異常,返回True, 會屏蔽異常class Context: def __enter__(self): print('enter context') def __exit__(self, *args, **kwargs): print('exit context') return 'haha' with Context() as c: print(c) # 輸出 enter context None exit context
__exit__
的三個參數 異常類型, 異常, tracebackclass Context: def __enter__(self): print('enter context') def __exit__(self, *args, **kwargs): print('exit context') print(args) print(kwargs) with Context(): pass # 輸出 enter context exit context (None, None, None) {}
args輸出三個None,表示三個位置參數,kwargs爲空,表示沒有關鍵字參數。
with Context(): raise Exception() enter context exit context (<class 'Exception'>, Exception(), <traceback object at 0x7f28608fdc88>) {} --------------------------------------------------------------------------- Exception Traceback (most recent call last) <ipython-input-145-c1afee4bfdab> in <module>() 1 with Context(): ----> 2 raise Exception() Exception:
__exit__
的三個參數:exc_type,exc_value,tracebackclass Context: def __enter__(self): print('enter context') def __exit__(self, exc_type, exc_value, traceback): print('exit context') print('exception type: {}'.format(exc_type)) print('exception value: {}'.format(exc_value)) print('exception traceback: {}'.format(traceback)) return True with Context(): raise TypeError('hahaha') # 輸出 enter context exit context exception type: <class 'TypeError'> exception value: hahaha exception traceback: <traceback object at 0x7fd257c18608>
with 語句適用於對資源進行訪問的場合,確保無論使用過程當中是否發生異常都會執行必要的「清理」操做,釋放資源,好比文件使用後自動關閉、線程中鎖的自動獲取和釋放等。即凡是在代碼塊先後插入代碼的場景通通適用
如下以計時器爲例
from functools import wraps class Timeit: def __init__(self, fn=None): wraps(fn)(self) def __call__(self, *args, **kwargs): start = datetime.datetime.now() ret = self.__wrapped__(*args, **kwargs) cost = datetime.datetime.now() - start print(cost) return ret def __enter__(self): self.start = datetime.datetime.now() def __exit__(self, *args): cost = datetime.datetime.now() - self.start print(cost) with Timeit(): z = 3 + 8 # 輸出0:00:00.000037 @Timeit def add(x, y): return x + y add(3, 8) # 輸出0:00:00.000044 返回11
總共實現了兩種計時方式,既能夠對語句塊計時,也能夠對函數計時。
contextlib是個比with優美的東西,也是提供上下文管理機制的模塊,它是經過Generator裝飾器實現的,再也不是採用__enter__
和__exit__
。contextlib中的contextmanager做爲裝飾器來提供一種針對函數級別的上下文管理機制。
import contextlib @contextlib.contextmanager def context(): print('enter context') # 初始化部分 至關於 __enter__ 方法 try: yield 'haha' # 至關於__enter__的返回值 finally: print('exit context') # 清理部分, 至關於 __exit__ 方法 with context() as c: print(c) raise Exception() # 輸出 enter context haha exit context --------------------------------------------------------------------------- Exception Traceback (most recent call last) <ipython-input-189-4c1dae6b647a> in <module>() 1 with context() as c: 2 print(c) ----> 3 raise Exception() Exception:
yield後面必須配合finally使用,不然若是拋出異常,程序不會執行yield後面的部門,也就是不會執行__exit__
部分。
python的反射,核心本質其實就是利用字符串的形式去對象(模塊)中操做(查找/獲取/刪除/添加)成員,就是一種基於字符串的事件驅動!
關於模塊的python反射以及反射機制分析參見:python反射機制深刻分析
如下主要分析類對象的反射機制
三個函數的原型:
主要做用是經過對象的成員名稱獲取對象的成員
class Point: def __init__(self, x, y): self.x = x self.y = y def print(self, x, y): print(x, y) p = Point(3, 5) p.__dict__['x'] # 返回3, 對於屬性來講,能夠經過 __dict__ 獲取 getattr(p, 'print')(3, 5) # 成員方法沒法經過__dict__獲取,可是能夠經過getattr函數獲取 # p.print(3, 5) getattr(p, 'x') # getattrr 也能夠獲取到屬性 setattr(p, 'haha', 'abcd') # p.haha = 'abcd',給對象p增長屬性haha p.haha # 返回abcd hasattr(p, 'print') # 返回True
setattr的對象是實例,若是要給實例動態增長方法,須要先把函數轉化爲方法,轉化的方法以下:
import types def mm(self): print(self.x) setattr(p, 'mm', types.MethodType(mm, p)) # 將mm函數轉化爲對象p的方法以後,再給p增長 p.mm() # 輸出3
使用getattr setattr hasattr 實現一個命令路由器:
class Command: def cmd1(self): print('cmd1') def cmd2(self): print('cmd2') def run(self): while True: cmd = input('>>>').strip() if cmd == 'quit': return getattr(self, cmd, lambda :print('not found cmd {}'.format(cmd)))() command = Command() command.run() # 輸出 >>>cmd1 cmd1 >>>cmd2 cmd2 >>>cmd3 not found cmd cmd3 >>>quit
__getattr__
__setattr__
__delattr__
__getattr__
方法時,若是訪問不存在的成員,會調用__getattr__
方法class A: def __init__(self): self.x = 3 a = A() a.x # 返回3 a.y # 若是沒有實現__getattr__方法,當訪問不存在的成員時會報錯 --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-228-cc7049c6eeec> in <module>() ----> 1 a.y AttributeError: 'A' object has no attribute 'y'
增長__getattr__
方法
class A: def __init__(self): self.x = 3 def __getattr__(self, name): return 'missing property {}'.format(name) a = A() a.x # 返回3 a.y # 返回'missing property y'。即訪問不存在的成員,會調用__getattr__方法
__setattr__
時, 任何地方對這個類的對象增長屬性,或者對現有屬性賦值,都會調用__setattr__
class A: def __init__(self): self.x = 3 def __setattr__(self, name, value): print('set {} to {}'.format(name, value)) setattr(self, name, value) a = A() a.x # 返回3 a.y = 5 # 輸出set y to 5
__delattr__
方法時,刪除其實例的屬性,會調用此方法class A: def __init__(self): self.x = 3 def __delattr__(self, name): print('you cannot delete property: {}'.format(name)) a = A() a.x # 返回3 del a.x # 輸出you cannot delete property: x
記得幫我點贊哦!
精心整理了計算機各個方向的從入門、進階、實戰的視頻課程和電子書,按照目錄合理分類,總能找到你須要的學習資料,還在等什麼?快去關注下載吧!!!
念念不忘,必有迴響,小夥伴們幫我點個贊吧,很是感謝。
我是職場亮哥,YY高級軟件工程師、四年工做經驗,拒絕鹹魚爭當龍頭的斜槓程序員。
聽我說,進步多,程序人生一把梭
若是有幸能幫到你,請幫我點個【贊】,給個關注,若是能順帶評論給個鼓勵,將不勝感激。
職場亮哥文章列表:更多文章
本人全部文章、回答都與版權保護平臺有合做,著做權歸職場亮哥全部,未經受權,轉載必究!