魔術方法
查看類的魔術方法
class A:
pass
dir(A) # 可以得到類所有公有成員
輸出結果如下
['__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__就會自動執行,
運算子多載
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__,
- 具體運算子對應的多載函式可以參考int類中運算子多載的實作:help(int)
不要過度使用運算子多載
Point.__add__ = lambda self, value: self - value
p = Point(3, 5) + Point(4, 6)
print(p.x, p.y) # 輸出-1, -1
__add__的具體實作如果寫成了減法,這種型別的錯誤非常不容易發現,因此如果不是在寫庫給第三方使用的時候,基本用不上運算子多載,
hash
- 使用內置函式
hash對某個物件求hash值時, 會呼叫物件的__hash__方法,示例代碼如下
In [1]: class Point:
...: def __hash__(self):
...: return 1
...:
In [2]: hash(Point())
Out[2]: 1
__hash__方法必須回傳int,否則會拋出TypeError
In [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物件,就是具有
__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__方法的話,這個類的每個物件,通常具有不同的hash
In [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__方法必須回傳非負整數
lst = [1, 2, 3]
len(lst) # 回傳3
lst.__len__() # 回傳3
因此內置函式和__len__方法的效果相同,
class Sized:
def __len__(self):
return 10
len(Sized()) # 回傳10
bool
- 當物件o實作了
__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
- 當物件o沒有實作
__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
- 當物件o既沒有實作
__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 = https://www.cnblogs.com/CHLL55/archive/2020/10/04/{}
@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 = https://www.cnblogs.com/CHLL55/archive/2020/10/04/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__ 方法,也就是說在這個陳述句塊的前后會執行一些操作,因此也叫背景關系,
- 即使with塊拋出例外,
__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)
with塊的as字句
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__的三個引數 例外型別, 例外, traceback
class 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,traceback
class 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
總共實作了兩種計時方式,既可以對陳述句塊計時,也可以對函式計時,
contextmanager的使用
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反射機制深入分析
以下主要分析類物件的反射機制
getattr setattr hasattr
三個函式的原型:
- getattr:getattr(object, name[, default]) -> value,getattr(x, 'y')等效于x.y
- setattr:setattr(obj, name, value, /),setattr(x, 'y', v)等效于x.y = v
- hasattr:hasattr(obj, name, /)
主要作用是通過物件的成員名稱獲取物件的成員
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高級軟體工程師、四年作業經驗,拒絕咸魚爭當龍頭的斜杠程式員,
聽我說,進步多,程式人生一把梭
如果有幸能幫到你,請幫我點個【贊】,給個關注,如果能順帶評論給個鼓勵,將不勝感激,
職場亮哥文章串列:更多文章

本人所有文章、回答都與著作權保護平臺有合作,著作權歸職場亮哥所有,未經授權,轉載必究!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/155580.html
標籤:其他
上一篇:Python面向物件的魔術方法
