Python 魔術方法小結
Python 類中的雙下方法
從常用的開始:快被自己蠢哭了
一、
_ _ init __
例項化物件時呼叫
class Foo:
def __init__(self,name):
self.name = name
f = Foo('CharmNight') # 這時候就呼叫__init__方法
_ _ new__
建立類物件時呼叫
_ _ new __ ()方法始終都是 類的靜態方法,即使沒有被加上靜態方法裝飾器
class Foo:
def __new__(cls,*args,**kwargs):
return super().__new__(cls)
def __init__(self):
pass
f = Foo()
__new__() 方法建立物件,在__init__()方法之前被呼叫,返回一個self物件,並將該物件傳給__init__()的第一個引數。一般不需要複寫__new__()方法,如果有需求:例如 單例模式可以通過重寫__new__方法 或者在類建立時進行一些修改
單例模式
當然單例模式不止這一種寫法,有很多方法都可以實現單例
# 通過__new__()方法實現單例模式
class Foo:
_instance = None
def __new__(cls, *args, **kwargs) :
if not hasattr(Foo,'_instance'):
cls._instance = super().__new__(cls)
return cls._instance
_ _ call__
Python中的函式是一級物件。這意味著Python中的函式的引用可以作為輸入傳遞到其他的函式/方法中,並在其中被執行。
而Python中類的例項(物件)可以被當做函式對待。也就是說,我們可以將它們作為輸入傳遞到其他的函式/方法中並呼叫他們,正如我們呼叫一個正常的函式那樣。而類中__call()__
函式的意義正在於此。為了將一個類例項當做函式呼叫,我們需要在類中實現__call()__
def __call__(self, [args...])
。這個方法接受一定數量的變數作為輸入。
__call()__
的作用是使例項能夠像函式一樣被呼叫,同時不影響例項本身的生命週期(__call()__
不影響一個例項的構造和析構)。但是__call()__
可以用來改變例項的內部成員的值
說人話就是_ _ call __方法就是 讓物件加括號 執行call裡的內容
class Foo:
def __init__(self, name):
self.name = name
def __call__(self, *args, **kwargs):
self.name = self.name.upper()
f = Foo('Charm_Night')
f()
print(f.name)
CHARM_NIGHT
二、
_ _ getattr__
攔截點號運算。當對未定義的屬性名稱和例項進行點號運算時,就會用屬性名作為字串呼叫這個方法。如果繼承樹可以找到該屬性,則不呼叫此方法
class Foo():
def __init__(self, item):
self.item = item
def __getattr__(self, item):
print('Run getattr')
return item
f = Foo([1,2,3])
print(f.name)
Run getattr
name
哈?說沒看懂什麼時候呼叫? 簡單說就是當呼叫該屬性卻沒有找到時,就會執行_ _ getattr _ _方法
如果屬性查詢(attribute lookup)在例項以及對應的類中(通過__dict__)失敗, 那麼會呼叫到類的__getattr__函式, 如果沒有定義這個函式,那麼丟擲AttributeError異常。由此可見,__getattr__一定是作用於屬性查詢的最後一步,兜底
例子
_ _ getattr _ _ 使得實現adapter wrapper模式非常容易,我們都知道“組合優於繼承”,_ _ getattr __實現的adapter就是以組合的形式。
class adaptee(object):
def foo(self):
print( 'foo in adaptee')
def bar(self):
print ('bar in adaptee')
class adapter(object):
def __init__(self):
self.adaptee = adaptee()
def foo(self):
print('foo in adapter')
self.adaptee.foo()
def __getattr__(self, name):
print('run')
return getattr(self.adaptee, name)
if __name__ == '__main__':
a = adapter()
a.foo()
a.bar()
foo in adapter
foo in adaptee
bar in adaptee
_ _ getattribute__
_ _ getattribute__是訪問屬性的方法,我們可以通過方法重寫來擴充套件方法的功能。
對於python來說,屬性或者函式都可以被理解成一個屬性,且可以通過_ _ getattribute__獲取。
當獲取屬性時,直接return object._ getattribute_(self,item) 或者使用 super(). _ _ getattribute _ _(item)
例子
class Foo:
def __init__(self):
self.name = 'Night'
def __getattribute__(self, item):
print('Run getattribute')
if item is 'func':
return super().__getattribute__('func')
elif item is 'name':
return super().__getattribute__('name')
else:
return super().__getattribute__('demo')
def demo(self):
print('Run demo')
def func(self):
print('Run func')
f = Foo()
print(f.name)
f.func()
f.a()
Run getattribute
Night
Run getattribute
Run func
Run getattribute
Run demo
getattr和aetattribute區別
首先我們應該已經知道了 __getattr__ 和 __getattribute__ 這兩個方法都是 擷取屬性的內建方法。我們來看下這兩個方法的區別
定義的區別
__getattr__ 是當找不到這個屬性時呼叫該方法
__getattribute__ 是當查詢屬性時就呼叫該方法
呼叫時的區別
如果類中同時實現了__getattr__ 和 __getattribute__ 方法 會呼叫__getattribute__ 方法
當然 如果 __getattribute__ 方法沒有找到(丟擲異常AttributeError) 會繼續呼叫__getattr__方法
class Foo:
def __getattr__(self, item):
print('getattr is running')
if item is 'func':
return getattr(self, item)
else:
return getattr(self, 'demo')
def __getattribute__(self, item):
print('getattribute is running')
if item is 'func':
return super().__getattribute__(item)
else:
return super().__getattribute__('demo')
def func(self):
print('func')
def demo(self):
print('demo')
f = Foo()
f.func2()
getattribute is running
func
_ _ setattr__
會攔截所有屬性的的賦值語句.如果定義了這個方法,self.arrt = value 就會變成self._ _ setattr__(“attr”, value)
class Foo:
def __setattr__(self, key, value):
print(key, value)
self.__dict__[key] = value
f = Foo()
f.name = 'Night'
print(f.name)
name Night
Night
錯誤使用方法
不可使用self.attr = value,因為他會再次呼叫self._ setattr_(“attr”, value),則會形成無窮遞迴迴圈,最後導致堆疊溢位異常
class Foo:
def __setattr__(self, key, value):
print(key, value)
self.name = value
屬性私有化
class PrivateExc(Exception):
'''這裡自定義了一個異常'''
def __init__(self, key):
err = '{} is private, Not change'.format(key)
super().__init__(err)
class Privacy:
def __setattr__(self, key, value):
if key in self.privates:
raise PrivateExc(key)
else:
self.__dict__[key] = value
class Foo(Privacy):
privates = ['name']
def __init__(self, age):
self.__dict__['name'] = 'Night'
self.age = age
f = Foo(18)
print(f.age)
print(f.name)
f.name = 'Charm' # 報錯
print(f.name)
_ _ delattr__
刪除屬性時呼叫該方法
class Foo:
def __delattr__(self, item):
print('run delete')
f = Foo()
f.name = 'Charm'
del f.name
三、
_ _ eq__
使用==進行判斷,是否相等
class Foo:
def __eq__(self, other):
print('eq is run')
if self is other:
return True
return False
f1 = Foo()
f2 = Foo()
print(f1 == f2)
eq is run
False
比較大小的4個方法
__ge__ 大於等於
__gt__ 大於
__le__ 小於等於
__lt__ 小於
class Foo:
def __init__(self, age):
self.age = age
def __ge__(self, other):
'''大於等於'''
print('ge is run')
if self.age.__ge__(other.age):
return True
return False
def __gt__(self, other):
'''大於'''
print('gt is run')
if self.age.__gt__(other.age):
return True
return False
def __lt__(self, other):
'''小於'''
print('lt is run')
if self.age.__lt__(other.age):
return True
return False
def __le__(self, other):
'''小於等於'''
print('le is run')
if self.age.__le__(other.age):
return True
return False
f1 = Foo(12)
f2 = Foo(13)
print(f1 <= f2)
print(f1 < f2)
print(f1 > f2)
print(f1 >= f2)
le is run
True
lt is run
True
gt is run
False
ge is run
False
_ _ hash__
如果呼叫hash()時會自行呼叫該物件的_ _ hash__()方法
class Foo:
def __init__(self, sex):
self.name = 'CharmNight'
self.age = 18
self.sex = sex
def __hash__(self):
return hash(self.name)
f = Foo('N')
f2 = Foo('M')
print(hash(f))
print(hash(f2))
-278629965
-278629965
例子
100個Student物件,如果他們的姓名和性別相同,則認為是同一個人
class Student:
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
def __eq__(self, other):
if self.name is other.name and self.sex is other.sex:
return True
return False
def __hash__(self):
return hash(self.name+self.sex)
set_ = set()
for i in range(100):
stu = Student('CharmNight',i, 'N')
set_.add(stu)
print(set_)
{<__main__.Student object at 0x02D6AC90>}
解釋下
首先:set 是根據 hash值進行判斷是否相同,即 拿到hash值 在通過==進行判斷是否是同一個物件
根據set的這個特性:
重寫了 __eq__ 和 __hash__ 兩個方法
四、
_ _ getitem__
返回鍵對應的值
class Foo:
def __init__(self, item):
self.item = item
def __getitem__(self, item):
print('getitem is run', item)
return self.item[item]
f1=Foo(['Charm', 'Night', '2018', '加油'])
print(f1[0])
getitem is run 0
Charm
_ _ setitem__
給鍵設定對應的值
class Foo:
def __init__(self, item):
self.item = item
def __setitem__(self, key, value):
print('setitem is run')
self.item[key] = value
def __repr__(self):
return '{}'.format(self.item)
f = Foo(['Night', 'so', 'cool'])
f[0] = 'Charm'
print(f)
setitem is run
['Charm', 'so', 'cool']
_ _ delitem__
刪除給定鍵對應的元素
class Foo:
def __init__(self, item):
self.item = item
def __delitem__(self, key):
print('delitem is run')
del self.item[key]
f = Foo(['CharmNight', 'GG'])
del f[1]
print(f.item)
delitem is run
['CharmNight']
_ _ len__
返回元素的數量
class Foo:
def __init__(self, item):
self.item = item
def __len__(self):
print('len is run')
return len(self.item)
f = Foo([1,2,3,4])
print(len(f))
len is run
4
上述4個方法的小結
使用__getitem__ __setitem__ __delitem__ __len__ 及 自定義異常 簡單模擬了一個不可刪除的 列表類
class DelError(Exception):
def __init__(self):
err = '{} undelete'.format(self)
super().__init__(err)
class Foo:
def __init__(self, item):
if isinstance(item, list):
self.item = item
else:
raise TypeError
def __len__(self):
return len(self.item)
def __setitem__(self, key, value):
self.item[key] = value
def __getitem__(self, item):
start = 0
end = len(self.item)
if isinstance(item, int):
if start <= item < end:
return self.item[item]
raise KeyError
elif isinstance(item, slice):
slice_start = 0 if item.start is None else item.start
slice_stop = len(self.item) if item.stop is None else item.stop
slice_step = item.step
if slice_step is None:
if slice_start <= slice_stop:
return self.item[slice_start:slice_stop]
raise KeyError
elif slice_step > 0:
if slice_stop <= slice_stop:
return self.item[slice_start:slice_stop:slice_step]
raise KeyError
else:
slice_start = len(self.item) if item.start is None else item.stop
slice_stop = 0 if item.stop is None else item.start
if slice_start >= slice_stop:
return self.item[slice_start:slice_stop:slice_step]
raise KeyError
def __delitem__(self, key):
raise DelError
f = Foo([1,2,3,4,5,6])
print(f[::-2])
del f[2]
五、
_ _ str__
改變物件的字串顯示
返回值必須是字串,否則丟擲異常
class Foo:
def __init__(self, name):
self.name = name
def __str__(self):
return 'obj name is ' + self.name
f = Foo('CharmNight')
print(f)
print('%s'%f)
print('%r'%f)
obj name is CharmNight
obj name is CharmNight
<__main__.Foo object at 0x02D19AB0>
_ _ repr__
改變物件的字串顯示
返回值必須是字串,否則丟擲異常
如果__str__沒有被定義,那麼就會使用__repr__來代替輸出
class Foo:
def __init__(self, name):
self.name = name
def __repr__(self):
return 'obj name is ' + self.name
f = Foo('CharmNight')
print(f)
print('%s'%f)
print('%r'%f)
obj name is CharmNight
obj name is CharmNight
obj name is CharmNight
_ _ format__
自定製格式化字串
class Foo:
def __init__(self):
self.name = 'CharmNight'
def __format__(self, format_spec):
return ' '.join([self.name, format_spec])
f = Foo()
print(format(f,'No.1'))
CharmNight No.1
六、
_ _ doc__
- 該屬性無法被繼承
- 類的描述資訊
class Foo:
'''Foo 是 一個類 233'''
pass
f = Foo()
print(f.__doc__)
Foo 是 一個類 233
_ _ module__
表示當前操作的物件在那個模組
class Foo:
'''Foo 是 一個類 233'''
pass
f = Foo()
print(f.__module__)
__main__ # 代表當前模組
_ _ class__
表示當前操作的物件的類是什麼
class Foo:
'''Foo 是 一個類 233'''
pass
f = Foo()
print(f.__class__)
<class '__main__.Foo'> # 模組.類名
七、
描述符是什麼:描述符本質就是一個新式類,在這個新式類中,至少實現了_ _ get_ _ (),_ _ set_ _ (),_ _ delete_ _ ()中的一個,這也被稱為描述符協議
_ _ get__():呼叫一個屬性時,觸發
_ _ set__():為一個屬性賦值時,觸發
_ _ delete__():採用del刪除屬性時,觸發
_ _ get__
class Foo:
def __init__(self):
self.name = 'Night'
def __get__(self, instance, owner):
print(instance, owner)
return instance
class Foo2:
f = Foo()
f2 = Foo2()
print(f2.f)
print(Foo2.f)
<__main__.Foo2 object at 0x0319AB90> <class '__main__.Foo2'>
<__main__.Foo2 object at 0x0319AB90>
None <class '__main__.Foo2'>
None
_ _ set__
_ _ delete__
_ _ del__
析構方法,當物件在記憶體中被釋放時,自動觸發執行。
注:如果產生的物件僅僅只是python程式級別的(使用者級),那麼無需定義 _ _ del _ _ ,如果產生的物件的同時還會向作業系統發起系統呼叫,即一個物件有使用者級與核心級兩種資源,比如(開啟一個檔案,建立一個數據庫連結),則必須在清除物件的同時回收系統資源,這就用到了_ del _
class Foo:
def __del__(self):
print('執行我啦')
f1=Foo()
del f1
print('------->')
#輸出結果
執行我啦
------->
注意
即使你沒有執行del 方法呼叫回收,直譯器也會在指令碼執行結束後呼叫系統的del方法進行回收
class Foo:
def __del__(self):
print('執行我啦')
f1=Foo()
print('------->')
#輸出結果
------->
執行我啦
經典場景
建立資料庫類,用該類例項化出資料庫連結物件,物件本身是存放於使用者空間記憶體中,而連結則是由作業系統管理的,存放於核心空間記憶體中
當程式結束時,python只會回收自己的記憶體空間,即使用者態記憶體,而作業系統的資源則沒有被回收,這就需要我們定製_ del_,在物件被刪除前向作業系統發起關閉資料庫連結的系統呼叫,回收資源
八、
_ _ enter _ _ 和 _ _ exit_ _
自定義支援上下文管理協議的類。自定義的上下文管理器要實現上下文管理協議所需要的 _ _ enter_ _ () 和 _ _ exit_ _ () 兩個方法:
- context_manager. _ _ enter _ _ () :進入上下文管理器的執行時上下文,在語句體執行前呼叫。with 語句將該方法的返回值賦值給 as 子句中的 target,如果指定了 as 子句的話
- context_manager. _ _ exit _ (exc_type, exc_value, exc_traceback):退出與上下文管理器相關的執行時上下文,返回一個布林值表示是否對發生的異常進行處理。引數表示引起退出操作的異常,如果退出時沒有發生異常,則3個引數都為None。如果發生異常,返回 True 表示不處理異常,否則會在退出該方法後重新丟擲異常以由 with 語句之外的程式碼邏輯進行處理。如果該方法內部產生異常,則會取代由 statement-body 中語句產生的異常。要處理異常時,不要顯示重新丟擲異常,即不能重新丟擲通過引數傳遞進來的異常,只需要將返回值設定為 False 就可以了。之後,上下文管理程式碼會檢測是否 _ exit__() 失敗來處理異常
class Foo:
def __init__(self, name):
self.name = name
def __enter__(self):
print('出現with語句,物件的__enter__被觸發,有返回值則賦值給as宣告的變數')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print('with中程式碼塊執行完畢時執行我啊')
print('exc_type',exc_type) # 異常型別
print('exc_val',exc_val) # 異常值
print('exc_tb',exc_tb) # 追溯資訊
return True # 返回值為True,那麼異常會被清空,就好像啥都沒發生一樣,with後的語句正常執行
with Foo('CharmNight') as f:
print('run')
raise AttributeError('asd')
print('over')
出現with語句,物件的__enter__被觸發,有返回值則賦值給as宣告的變數
run
with中程式碼塊執行完畢時執行我啊
exc_type <class 'AttributeError'>
exc_val asd
exc_tb <traceback object at 0x034176C0>
over
例子
簡單模擬下with Open的語法
class Open:
def __init__(self, file, mode='r', encoding=None):
self.file = file
self.mode = mode
self.encoding = encoding
def __enter__(self):
self.f = open(self.file, mode=self.mode, encoding=self.encoding)
return self.f
def __exit__(self, exc_type, exc_val, exc_tb):
self.f.close()
return True
with Open('asd', 'w', encoding='utf-8') as f:
f.write('Hello')
九、
_ _ slots__
_ _ slots_ _ 的作用是阻止在例項化類時為例項分配dict,預設情況下每個類都會有一個dict,通過_ _ dict_ _ 訪問,這個dict維護了這個例項的所有屬性
作用:
- 減少記憶體使用
- 限制對例項新增新的屬性
缺點:
- 不可被繼承
- 不可動彈新增新屬性
例子
class Foo:
__slots__ = ('name', 'age')
def __init__(self, name, age):
self.name = name
self.age = age
f = Foo('CharmNight', 18)
print(f.name)
print(f.age)
f.sex = 'N' # 報錯
Traceback (most recent call last):
CharmNight
File "D:/demo.py", line 12, in <module>
18
f.sex = 'N'
AttributeError: 'Foo' object has no attribute 'sex'
如何減少記憶體的使用
為何使用__slots__:字典會佔用大量記憶體,如果你有一個屬性很少的類,但是有很多例項,為了節省記憶體可以使用__slots__取代例項的__dict__當你定義__slots__後,__slots__就會為例項使用一種更加緊湊的內部表示。例項通過一個很小的固定大小的陣列來構建,而不是為每個例項定義一個字典,這跟元組或列表很類似。在__slots__中列出的屬性名在內部被對映到這個陣列的指定小標上。使用__slots__一個不好的地方就是我們不能再給例項新增新的屬性了,只能使用在__slots__中定義的那些屬性名。
class Foo:
__slots__ = ('name', 'age')
def __init__(self, name, age):
self.name = name
self.age = age
f = Foo('CharmNight', 18)
print(f.__slots__)
print(f.__dict__)
('name', 'age')
Traceback (most recent call last):
File "D:/demo.py", line 10, in <module>
print(f.__dict__)
AttributeError: 'Foo' object has no attribute '__dict__'
十、
_ _ next__ 和 _ _ iter _ _
class Foo:
def __init__(self):
self.nub = 0
def __next__(self):
print('next is run')
self.nub += 1
return self.nub
def __iter__(self):
print('iter is run')
return self
f = Foo()
for i in f:
print(i)
十一、
_ _ add__
加法運算就是呼叫的_ _ add __
class Foo:
def __init__(self, nub):
self.nub = nub
def __add__(self, other):
return self.nub + other.nub
f1 = Foo(1)
f2 = Foo(10)
a = f1 + f2
print(a)
11
_ _ iadd__
就地加呼叫的_ _ iadd __
class Foo:
def __init__(self, nub):
self.nub = nub
def __iadd__(self, other):
return self.nub + other.nub
f1 = Foo(1)
f2 = Foo(10)
f1 += f2
print(f1)
11
_ _ radd__
被加(當左運算元不支援相應的操作時被呼叫)
class Foo:
def __init__(self, nub):
self.nub = nub
def __radd__(self, other):
print('radd is run')
return self.nub + other
f1 = Foo(1)
s = 10 + f1
print(s)
11
當然還有其他的 …… 小白太low 還沒有接觸到