一篇掌握python魔法方法詳解

本編研究下python的內置屬性/魔法方法/魔法函數

這裏說的內置屬性,是指__xxx__的方法和屬性,有的地方也稱爲魔法方法,叫法不一樣。

本文概要

  • 1.__init____new__的順序、使用?
  • 2.__getattribute__幹嘛的?
  • 3.__call__作用,與callable內置函數有着怎樣的關係呢?
  • 4.對象如何才能比較? __lt____gt__
  • 5.__getattr____setattr____delattr__何時觸發?
  • 6.__add____iadd__區別,何時觸發(++=)?什麼時候會改變自身,什麼時候會新建對象?
  • 7.__getitem____setitem____delitem__何時觸發?
  • 8.迭代__iter____next__for-in循環的配合
  • 9.__mul____rmul____imul__何時觸發?什麼時候會改變自身,什麼時候會新建對象?

如果上面幾點問題都能回答上,那麼可以跳過本篇文章了。本篇相關文章共三連彈。(端午節在家整理,有幫助記得點個👍,就是對我最大的肯定😘😘😘)

技巧:文章內容太多如何閱讀?

  • 先看文章的目錄(TOC),掌握文章整體脈絡
  • 然後走馬觀花式,通篇閱讀。如果文章有用,再細細研究;文章沒用的直接忽略,找下篇。
    • 如果爲了解決問題的,解決完了有時間再細讀
    • 如果學習的,收藏起來,邊學習,邊敲代碼實踐 (別copy代碼,自己敲)
  • 收藏完,下次用的時候,忘記如何使用了,到文章直接CTRL+F查找關鍵字

這提到鏈接收藏功能,推薦下 ㊙鏈接管理工具🔗 裏的【我的鏈接】可以管理自己的鏈接哦 (個人鏈接屬於私人資產,所以要註冊個賬戶才能使用哦👇👇👇)
我的鏈接

浩瀚的網絡中,你我的相遇也是種緣分,看你天資聰慧、骨骼精奇,就送你一場大造化吧,能領悟多少就看你自己了。㊙傳承之地🙇

1.常見魔法函數

常用專有屬性 說明 觸發方式
__init__ 構造初始化函數 創建實例後,賦值時使用,在__new__
__new__ 生成實例所需屬性 創建實例時
__class__ 實例所在的類 實例.__class__
__str__ 實例字符串表示,可讀性 print(類實例),如沒實現,使用repr結果
__repr__ 實例字符串表示,準確性 類實例 回車 或者 print(repr(類實例))
__del__ 析構 del刪除實例
__dict__ 實例自定義屬性 vars(實例.__dict__)
__doc__ 類文檔,子類不繼承 help(類或實例)
__getattribute__ 屬性訪問攔截器 訪問實例屬性時
__delattr__(s,name) 刪除name屬性 調用時
__gt__(self,other) 判斷self對象是否大於other對 調用時
__setattr__(s,name,value) 設置name屬性 調用時
__gt__(self,other) 判斷self對象是否大於other對象 調用時
__lt__(slef,other) 判斷self對象是否小於other對象 調用時
__ge__(slef,other) 判斷self對象是否大於或者等於other對象 調用時
__le__(slef,other) 判斷self對象是否小於或者等於other對象 調用時
__eq__(slef,other) 判斷self對象是否等於other對象 調用時
__call__(self,\*args) 把實例對象作爲函數調用 調用時

下面分別以object和list的內置屬性(魔法方法)來講解下,其他的有需要的可以留言補充。

2.object

使用dir()函數來可以打印出有哪些屬性,下面會挑一些常用的演示

演示

class People:
    name: str

class MyTestCase(unittest.TestCase):
    def test_attr(self):
       print(dir(People))

源碼座標

如需要源碼,可以查看。(關鍵代碼都貼在文章裏,不用看也行)

2-1.init

初始化時,調用__init__方法

演示

class People:
    name: str = 'iworkh'

    def __init__(self, name):
        print('call...__init__')
        self.name = name


class MyTestCase(unittest.TestCase):

    def test(self):
        people = People('沐雨雲樓') # 創建對象時,調用__init__
        print(people.name)

2-2.new

__ new__ ()__ init__()之前被調用,用於生成實例對象.

利用這個方法和類屬性的特性可以實現設計模式中的單例模式.單例模式是指創建唯一對象嗎,單例模式設計的類只能實例化一個對象.

演示

class People:
    name: str = 'iworkh'

    def __new__(cls, *args, **kwargs):
        print('call...__new__')
        obj = object.__new__(cls)
        return obj

    def __init__(self, name):
        print('call...__init__')
        self.name = name


class MyTestCase(unittest.TestCase):

    def test(self):
        people = People('沐雨雲樓')  # 創建對象先調用__new__,初始化時調用__init__
        print(people.name)

2-3.str

__ str__ ()用於表示對象代表的含義,返回一個字符串.實現了__ str__ ()方法.

  • 可以直接使用print語句輸出對象,
  • 可以通過函數str()觸發__ str__ ()的執行.這樣就把對象和字符串關聯起來,便於某些程序的實現,可以用這個字符串來表示某個類

演示

class People:
    name: str = 'iworkh'
    age: int = 20

    def __str__(self) -> str:
        print("call...__str__")
        return "{{'name': {}, 'age': {}}}".format(self.name, self.age)


class MyTestCase(unittest.TestCase):
    def test(self):
        people = People()
        print(people) # {'name': iworkh, 'age': 20}
        print(str(people)) # {'name': iworkh, 'age': 20}

2-4.doc

類文檔,子類不會繼承

help(類或實例)會觸發

演示

class People:
    """
    人
    """
    name: str = 'iworkh'
    age: int = 20

    def say(self):
        """
        say method
        """
        print('say')


class MyTestCase(unittest.TestCase):
    def test(self):
        people = People()
        print(People.__doc__)  # 類: 人
        print(people.__doc__)  # 對象:人
        print(People.say.__doc__)  # 類:say method
        print(people.say.__doc__)  # 對象:say method
        print(help(People.say))  # 類:say method

2-5.dict

實例自定義屬性,只有_init__和通過對象.xxx賦值的屬性,纔會顯示

vars(實例)會觸發

演示

class People:
    name: str = 'iworkh'
    age: int = 20
    admin: bool = True

    def __init__(self):
        self.admin = False

class MyTestCase(unittest.TestCase):
    def test(self):
        peo = People()
        print(peo.__dict__)  # {'admin': False},只有init和塞值的纔會顯示
        print(vars(peo))  # {'admin': False}
        # 塞值後
        peo.name = '沐雨雲樓'
        print(peo.__dict__)  # 對象的__dict__ , {'admin': False, 'name': '沐雨雲樓'}
        print(vars(peo))  # {'admin': False, 'name': '沐雨雲樓'}
        print(People.__dict__)  # 類的__dict__, 很多,還包括__和自己定義的

2-6.getattribute

屬性訪問攔截器

演示

class People:
    name: str = 'iworkh'
    password = 'iworkh123'
    age: int = 20

    def __getattribute__(self, item):
        print("call .... __getattribute__")
        if item == 'password':
            return '保密'
        else:
            return object.__getattribute__(self, item)


class MyTestCase(unittest.TestCase):
    def test(self):
        peo = People()
        print(peo.name)  # iworkh
        print(peo.password)  # 保密

警告

不要在__getattribute__方法中調用self.xxxx,會死循環,而應該使用object.__getattribute__(self, item)

2-7.name

__name__ 返回類名

演示

class People:
    name: str = 'iworkh'


class MyTestCase(unittest.TestCase):

    def test_name(self):
        print(People.__name__)  # 類 People

2-8.屬性操作

屬性操作,無非就塞值、取值、刪除屬性

  • __setattr__ 設置屬性,設置屬性值時,觸發這個方法 (對象.屬性, setattr)
  • __getattr__ 取屬性,當屬性不存在時,纔會觸發這個方法(對象.屬性,getattr)
  • __delattr__ 刪除屬性,刪除屬性值時,觸發這個方法(delattr, del)

演示

class People:
    name: str = 'iworkh'
    password = 'iworkh123'

    def __getattr__(self, item):
        print('call...__getattr__')
        return 'no attribute'

    def __delattr__(self, item):
        print('call...__delattr__')
        object.__delattr__(self, item)

    def __setattr__(self, key, value):
        print('call...__setattr__')
        object.__setattr__(self, key, value)


class MyTestCase(unittest.TestCase):
    def test(self):
        peo = People()
        setattr(peo, 'name', '沐雨雲樓')  # 塞值
        # 取值
        print(peo.name)  # 存在, 不會觸發__getattr__
        print(peo.sex)  # 不存在,調用 __getattr__
        print(getattr(peo, 'name'))  # 存在, getattr方式 不會觸發__getattr__
        print(getattr(peo, 'sex'))  # 不存在,getattr方式 調用 __getattr__
        # 刪除
        peo.password = "test_password"  # 塞值
        del peo.password
        delattr(peo, 'name')

2-9.call

定義__call__類實例對象可以像調用普通函數那樣,以對象名()的形式使用。

關於call的可以閱讀下這篇文章: Python __call__詳解

演示

class People:
    name: str = 'iworkh'
    age: int = 20

    def __call__(self, **kwargs):
        print('call...__call__')
        self.name = kwargs['name']
        self.age = kwargs['age']


class MyTestCase(unittest.TestCase):
    def test(self):
        peo1 = People()
        print(peo1.name, peo1.age, sep="......") # iworkh......20
        peo1(name='沐雨雲樓', age=22) # 通過對象()方式就能修改值
        print(peo1.name, peo1.age, sep="......") # 沐雨雲樓......22

思考:
如何取得類/對中所有屬性或者方法?

  • dir:屬性和方法都拿到
  • hasattr: 屬性和方法都拿到
  • callable: 屬性爲False, 函數和方法是True

這有實現好的例子 python對象與dict互轉

裏面的obj_dict_tool.py類的to_dic方法

還可以看下這篇文章call方法,使用了__call__方式

2-10.repr

print的類如果沒有__str__,那麼就會調用__repr__

演示

class People:
    name: str = 'iworkh'
    age: int = 20

    def __str__(self) -> str:
        print("call...__str__")
        return "str --> {{'name': {}, 'age': {}}}".format(self.name, self.age)

    def __repr__(self):
        print("call...__repr__")
        return "repr --> {{'name': {}, 'age': {}}}".format(self.name, self.age)


class MyTestCase(unittest.TestCase):
    def test(self):
        people = People()
        print(people)
        print(str(people))

__str__代碼註釋後再試下

2-11.比較

對象與對象比較時,得需要比較的方法才能判斷出大小,需要實現下面一些方法

  • __lt__: 小於 <
  • __gt__: 大於 >
  • __le__: 小於等於 <=
  • __ge__: 大於等於 >=
  • __eq__: 等於 =

當只定義了一個小於,沒有定義大於時。在使用時也可以使用大於,會根據小於結果取反的。

演示

class People:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __le__(self, other):
        print('call ... le')
        return self.age <= other.age


class MyTestCase(unittest.TestCase):
    def test(self):
        people_san = People('三哥', 30)
        people_three = People('張三', 30)
        people_four = People('李四', 40)
        people_five = People('王五', 50)
        print(people_three <= people_four) # True
        print(people_five >= people_three) # True
        print(people_san >= people_three) # True
        print(people_san <= people_three) # True
        print(people_three >= people_four) # False

3.list

我們再來看下list的一些屬性

屬性 說明
__add__ +,返回一個新列表
__iadd__ +=,原列表上操作
__contains__ in,定義當使用成員測試運算符(in 或 not in)時的行爲
__delitem__ 定義刪除容器中指定元素的行爲
__getitem__ 定義獲取容器中指定元素的行爲
__setitem__ 定義設置容器中指定元素的行爲
__iter__ for定義當迭代容器中的元素的行爲
__next__ for定義當迭代容器中的元素的行爲
__len__ 定義當被 len() 調用時的行爲
__mul__ 乘觸發,返回新列表
__rmul__ 乘觸發,右操作符,返回新列表
__imul__ 乘觸發,改變自己
append 在列表後面追加元素
extend 在列表後面追加另一個列表的元素
insert 指定位置插入元素
copy 複製
count list.count(data) 統計列表中出現data的次數
index list.count(data) 列表中元素的索引
pop list.pop(index) 彈出指定位置的元素
remove list.remove(obj) 刪除列表中指定元素
clear 清空list
reverse 列表元素倒序輸出
sort list.sort(reverse=False)列表元素升序

下面只演示模仿方法

使用dir()函數來可以打印出有哪些屬性,下面會挑一些常用的演示

演示

class MyTestCase(unittest.TestCase):
    def test_attr(self):
        print(dir([1,2,3]))

下面就來演示幾個常用的魔法方法,以Alphabet類爲例子,裏面有個data屬性爲list類型,來存字母。

源碼座標

如需要源碼,可以查看。(關鍵代碼都貼在文章裏,不用看也行)

3-1.添加

__add__ : 和+操作符對應,對象與對象相加,返回一個新列
__iadd__: 和+=操作符對應,添加一個元素,在原列表上操作

演示

class Alphabet:
    data = []

    def __init__(self, value) -> None:
        self.data = value

    def __add__(self, other):
        print('call...__add__')
        return Alphabet(self.data + other.data)

    def __iadd__(self, other):
        print("call...__iadd__")
        self.data += other
        return self

class MyTestCase(unittest.TestCase):
    def test(self):
        list_data = ['a', 'c', 'd']
        alphabet = Alphabet(list_data)
        print(alphabet.data)
        print("*" * 50)
        # + 與 __add__
        alphabet_add = Alphabet(['m', 'n'])
        alphabet_sum = alphabet + alphabet_add  # 返回一個新的對象
        print(alphabet.data)  # 原來的沒有變 ['a', 'c', 'd']
        print(alphabet_sum.data)  # ['a', 'c', 'd', 'm', 'n']
        print("*" * 50)
        # += 與 __iadd__
        alphabet += 'x'
        print(alphabet.data) # 在原對象上操作 ['a', 'c', 'd', 'x']

3-2.包含

__contains__:是否包含

演示

class Alphabet:
    data = []

    def __init__(self, value) -> None:
        self.data = value

    def __contains__(self, item):
        print("call...__contains__")
        return item in self.data


class MyTestCase(unittest.TestCase):
    def test(self):
        list_data = ['a', 'c', 'd']
        alphabet = Alphabet(list_data)
        print(alphabet.data)
        print('a' in alphabet) # True
        print('x' in alphabet) # False

3-3.元素操作

__getitem__:取得元素
__setitem__:設置元素
__delitem__:刪除元素

演示

class Alphabet:
    data = []

    def __init__(self, value) -> None:
        self.data = value

    def __getitem__(self, index):
        print('call...__getitem__')
        return self.data[index]

    def __setitem__(self, key, value):
        print('call...__setitem__')
        self.data[key] = value

    def __delitem__(self, key):
        print("call...__delitem__")
        delattr(self, key)


class MyTestCase(unittest.TestCase):
    def test(self):
        list_data = ['a', 'c', 'd']
        alphabet = Alphabet(list_data)
        print(alphabet.data)
        print("*" * 30) # ['a', 'c', 'd']
        # 取值
        print(alphabet[1])  # c 取值 會觸發__getitem__
        # 設置值
        alphabet[1] = 'b'  # 設值 會觸發__setitem__
        print(alphabet.data) # ['a', 'b', 'd']
        # 刪除
        del alphabet['data']  # 刪除 等價於 delattr(alphabet, 'data')  會觸發__delitem__
        print(alphabet.data) # []

3-4.迭代

__iter__:迭代器,返回自己,然後for可以循環調用next方法
__next__: 每一次for循環都調用該方法(必須存在)

演示

class Alphabet:
    data = []
    index = -1

    def __init__(self, value) -> None:
        self.data = value

    def __iter__(self):
        print("call...__iter__")
        return self

    def __next__(self):
        print("call...__next__")
        self.index += 1
        if self.index >= len(self.data):
            raise StopIteration()
        else:
            return self.data[self.index]


class MyTestCase(unittest.TestCase):
    def test(self):
        list_data = ['a', 'c', 'd']
        alphabet = Alphabet(list_data)
        print(alphabet.data)
        # for循環 和 __iter__ 以及 __next__配合
        for item in alphabet:
            print(item)

3-5.重複

__mul__: 左操作符相乘,返回新的list,原list不變
__rmul__: 右操作符相乘,返回新的list,原list不變
__imul__: 改變原list

演示

class Alphabet:
    data = []

    def __init__(self, value) -> None:
        self.data = value

    def __mul__(self, other):
        print("call...__mul__")
        return Alphabet(self.data * other)

    def __rmul__(self, other):
        print("call...__rmul__")
        return Alphabet(other * self.data)

    def __imul__(self, other):
        print("call...__imul__")
        return self.data * other


class MyTestCase(unittest.TestCase):
    def test(self):
        list_data = ['a', 'c', 'd']
        alphabet = Alphabet(list_data)
        print(alphabet.data)
        # __mul__
        alphabet_3 = alphabet * 3
        print(alphabet.data)
        print(alphabet_3.data)  # ['a', 'c', 'd', 'a', 'c', 'd', 'a', 'c', 'd']
        print("*" * 50)
        # __rmul__
        alphabet_r3 = 3 * alphabet
        print(alphabet.data)
        print(alphabet_r3.data)  # ['a', 'c', 'd', 'a', 'c', 'd', 'a', 'c', 'd']
        print("*" * 50)
        # __imul__
        alphabet *= 3  # 改變了原對象
        print(alphabet)  # ['a', 'c', 'd', 'a', 'c', 'd', 'a', 'c', 'd']

3-6.len

__len__: 這個簡單,但調用len(obj)會觸發

演示

class Alphabet:
    data = []

    def __init__(self, value) -> None:
        self.data = value

    def __len__(self):
        return len(self.data)


class MyTestCase(unittest.TestCase):
    def test(self):
        list_data = ['a', 'c', 'd']
        alphabet = Alphabet(list_data)
        print(len(alphabet)) # 3

4.總結

內置屬性(魔法方法)還有很多,只要記得一些常用的即可。

思考

  • 1.__init____new__的順序、使用
  • 2.__getattribute__幹嘛的?
  • 3.__call__作用,與callable內置函數有着怎樣的關係呢?
  • 4.對象如何才能比較? __lt____gt__
  • 5.__getattr____setattr____delattr__何時觸發?
  • 6.__add____iadd__區別,何時觸發(++=)?什麼時候會改變自身,什麼時候會新建對象?
  • 7.__getitem____setitem____delitem__何時觸發?
  • 8.迭代__iter____next__for-in循環的配合
  • 9.__mul____rmul____imul__何時觸發?什麼時候會改變自身,什麼時候會新建對象?

如果上面幾個問題能過回答上,那麼恭喜您,本節內容的精髓您都掌握了。

到這內置函數和內置屬性(魔法方法)都介紹完了,記得要配合 內置函數一起學習哦

5.推薦

能讀到文章最後,首先得謝謝您對本文的肯定,你的肯定是對我們的最大鼓勵。

你覺本文有幫助,那就點個👍
你有疑問,那就留下您的💬
怕把我弄丟了,那就把我⭐
電腦不方便看,那就把發到你📲

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章