『無為則無心』Python物件導向 — 59、魔法方法

繁華似錦Fighting發表於2022-03-01

在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()

圖解如下:

image

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()時。
  • 使用%sf'{}'拼接物件時。
  • 使用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__()方法在listdictsettuplestr等序列中都存在,而在intfloat類中是不存在的。

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人
"""

參考:

相關文章