在Python語言中,有些方法名比較特別,在名稱的前後各有兩個下劃線,這樣的方法往往具有特殊的意義,我們統稱為魔法方法,也叫特殊方法。需要注意的是,我們在建立自定義方法時要避免這樣的格式,防止造成不必要的衝突。
Python的魔法方法有很多,我們主要介紹常用的幾個魔法方法。以後需要用到其他的魔法方法,按照介紹的這幾個呼叫方式,自己嘗試一下就可以了,很簡單的。
1、魔法方法__new__()
類的例項化時,呼叫的第一個方法是__new__
方法。
官方定義如下,可以看到__new__
被定義成靜態方法,第一個必須傳入的引數是cls
,代表需要例項化的類。
class Demo():
def __new__(cls, *args, **kwargs):
print('呼叫__new__方法')
def __init__(self):
print('呼叫__init__方法')
# 結果:呼叫__new__方法
d = Demo()
從結果看到,只呼叫了__new__
方法,但奇怪的是並沒有呼叫__init__
方法,這是為什麼呢?
實際上,__new__
是負責對當前類進行例項化,並將例項返回,傳給__init__
方法。__init__
方法中的self
就是指代__new__
傳過來的物件,所以再次強調,__init__
是例項化後呼叫的第一個方法。
只有__new__
方法返回建立的例項物件,後邊的__init__
方法才能執行。
class Demo:
def __new__(cls, *args, **kwargs):
print('呼叫__new__方法')
return object.__new__(cls)
def __init__(self):
print('呼叫__init__方法')
# 建立物件
d = Demo()
"""
輸出結果:
呼叫__new__方法
呼叫__init__方法
"""
總結:
__new__()
方法是一個魔法方法(同時也是一種靜態方法),用於建立一個類的例項,返回一個類的例項物件,用於傳入初始化方法__init__()
方法中。一般並不需要對其進行宣告和重寫,若是重寫的話,需要注意返回一個有效的類的例項物件,例項通過object
父類呼叫類的例項化方法,用於返回一個物件例項。若是不返回一個類的例項物件,會導致類的__init__()
方法不會被呼叫,當然例項(self
為空)也不會被成功建立。
2、魔法方法__init__()
__init__
方法是類建立過程中用的比較多的魔法方法,是類物件建立後呼叫的初始化方法,緊跟者__new__
方法後呼叫,主要用於例項變數的初始化操作。
__init__
方法可以理解為一個類的例項的構造器,其方法的特點是不會返回任何物件或者值,若返回則會丟擲 TypeError
異常。
(1)體驗__init__()
思考:洗衣機的寬度高度是與生俱來的屬性,可不可以在生產過程中就賦予這些屬性呢?
答:理應如此。
__init__()
方法的作用:初始化物件。
示例:
"""
目標: 定義init方法設定初始化屬性並訪問呼叫
1. 定義類
init方法: width 和 height
新增例項方法:訪問例項屬性
2. 建立物件
3. 驗證成果
呼叫例項方法
"""
# 1.定義類
class Washer():
# 定義初始化功能的函式
def __init__(self):
# 新增例項屬性
self.width = 500
self.height = 800
def print_info(self):
# 類裡面呼叫例項屬性
print(f'洗衣機的寬度是{self.width}, 高度是{self.height}')
# 2. 建立物件
haier1 = Washer()
# 3. 呼叫例項方法
haier1.print_info()
注意:
__init__()
方法,在建立一個物件時預設被呼叫,不需要手動呼叫。__init__(self)
中的self引數,不需要開發者傳遞,Python直譯器會自動把當前的物件引用傳遞過去。
(2)帶引數的__init__()
思考:一個類可以建立多個物件,如何對不同的物件設定不同的初始化屬性呢?
答:傳引數。
# 定義類
class Washer():
# 定義帶引數的init方法
def __init__(self, width, height):
self.width = width
self.height = height
# 定義例項方法
def print_info(self):
# 呼叫例項屬性
print(f'洗衣機的寬度是{self.width}')
print(f'洗衣機的高度是{self.height}')
haier1 = Washer(10, 20)
haier1.print_info()
# 如果建立物件的時候不傳遞引數,會報錯。
# haier2 = Washer()
haier2 = Washer(100, 200)
haier2.print_info()
圖解如下:
3、魔法方法__del__()
當刪除物件時,Python直譯器也會預設呼叫__del__()
方法。__del__()
方法屬於解構函式。
__del__()
方法不會被主動呼叫,只有當類的例項物件的引用計數為0時,才會被呼叫,一般不建議重寫__del__()
方法。
示例
class Washer():
def __init__(self, width, height):
self.width = width
self.height = height
def __del__(self):
print(f'{self}物件已經被刪除')
haier1 = Washer(10, 20)
# <__main__.Washer object at 0x0000026118223278>物件已經被刪除
del haier1
即便不手動刪除物件,也能呼叫到
__del__()
方法,也就上面示例中,不編寫最後del haier1
語句,當Python程式執行完成後,也會列印一樣的結果,是Python直譯器自動呼叫的。
4、魔法方法__str__()
和__repr__()
-
Python 定義了
__str__()
和__repr__()
兩種方法,__str__()
用於顯示給使用者,而__repr__()
用於顯示給開發人員。 -
__str__
方法預設情況下,它會返回當前物件的類名+object at+記憶體地址
。
如下:<__main__.Students object at 0x0000000002443808>
-
有時候想讓螢幕列印的結果不是物件的記憶體地址,而是它的形式的字串,以便更直觀地顯示物件內容,可以通過在該物件的類中重寫
__str__()
或__repr__()
方法來實現。 -
注意:
__str__()
方法和__repr__()
方法的返回值只能是字串!
(1)關於呼叫兩種方法的時機
以下三種場景中,會優先呼叫物件的__str__()
方法。若沒有,就呼叫__repr__()
方法。若再沒有,則顯示其記憶體地址。
- 使用
print()
時。 - 使用
%s
和f'{}'
拼接物件時。 - 使用
str(x)
轉換物件x時。
對於下面兩種場景:
- 用
%r
進行字串拼接時。 - 用
repr(x)
轉換物件x時。
則會呼叫這個物件的__repr__()
方法。若沒有,則不再看其是否有__str__()
方法。若再沒有,則顯示其記憶體地址。
(2)示例
__str__
方法一般放置的是一些解釋說明的文字。
示例:
class Washer():
def __init__(self, width, height):
self.width = width
self.height = height
def __str__(self):
# return repr('這是海爾洗衣機的說明書')
return '這是海爾洗衣機的說明書'
# __repr__方法在實際工作總一般如下使用
__repr__ = __str__
haier1 = Washer(10, 20)
# 結果:這是海爾洗衣機的說明書
print(haier1)
5、魔法方法__call__()
物件後面加括號,觸發執行。
提示:
__init__
方法的執行是由建立物件觸發的,即:物件 = 類名()
。- 而對於
__call__
方法的執行是由物件後加括號觸發的,即:物件()
或者類()()
。
示例:
class Person(object):
def __init__(self):
print("__init__方法執行了")
def __call__(self, *args, **kwargs):
print("__call__方法中執行了")
# 1.物件 = 類名()
p = Person() # 執行__init__
# 2.物件() 呼叫__call__方法
p()
# 3.類()() 呼叫__call__方法
Person()() # 執行__call__
提示:這個方法在工作中的應用自己以後總結,先知道怎麼用就好。
6、魔法方法__len__()
__len__()
方法在list
,dict
,set
,tuple
,str
等序列中都存在,而在int
和float
類中是不存在的。
print('__len__' in dir(int)) # False
print('__len__' in dir(float)) # False
print('__len__' in dir(str)) # True
print('__len__' in dir(list)) # True
print('__len__' in dir(tuple)) # True
print('__len__' in dir(dict)) # True
print('__len__' in dir(set)) # True
如果一個類要表現得像一個list
一樣,有需求要獲取有多少個元素,就得用 len()
函式。
要讓 len()
函式工作正常,類中就必須提供一個魔法方法__len__()
方法,它的作用是返回元素的個數。
示例:
# 定義一個類,接收同學名的名字
class Students(object):
def __init__(self, *args):
self.names = args
def __len__(self):
return len(self.names)
stu = Students('唐僧', '孫悟空', '豬八戒', '沙和尚')
print(len(stu)) # 4
print(stu.__len__()) # 4
提示:只要在類中正確實現了
__len__()
方法,就可以用len()
函式。
7、魔法方法__getitem__()
、__setitem__()
、__delitem__()
在Python中,如果我們想實現建立類似於序列和對映的類,可以通過重寫魔法方法__getitem__()
方法、__setitem__()
方法、__delitem__()
方法去模擬。
示例:
class MyDict(object):
def __init__(self):
self.item = {}
# 獲取成員
def __getitem__(self, key):
return self.item.get(key)
# 新增成員
def __setitem__(self, key, value):
self.item[key] = value
# 刪除成員
def __delitem__(self, key):
del self.item[key]
# 獲取人員數量
def __len__(self):
return len(self.item)
if __name__ == "__main__":
# 建立序列物件
myDict = MyDict()
# 新增元素
myDict.__setitem__('師傅', '唐憎')
myDict.__setitem__('大師兄', '孫悟空')
# 檢視剛新增的成員
print(myDict.__getitem__('大師兄'))
print(f"現在成員{len(myDict)}人", ) # 2
# 修改成員
myDict.__setitem__('大師兄', '齊天大聖')
print(myDict.__getitem__('大師兄'))
print(f"現在成員{len(myDict)}人", ) # 2
# 刪除元素
myDict.__delitem__('師傅')
print(f"現在成員{len(myDict)}人", ) # 1
"""
輸出結果:
孫悟空
現在成員2人
齊天大聖
現在成員2人
現在成員1人
"""
參考: