歡迎訪問的個人博客,博客地址爲 https://haibei.online/
〇、魔術方法定義
- 在python語法中,往往你會看到有的名稱前面和後面都加上了雙下劃線,由這些名字組成的集合所包含的方法就叫做魔法方法,也叫做特殊方法。
- python的魔術方法非常強大,然而隨之而來的則是責任。瞭解正確的方法去使用非常重要!
一、總述
1、class類的老大哥 __new__
- 之所以稱之爲老大哥,是因爲
__new__
在類執行時第一個被調用,而不是__init__
- 如果
__new__
返回值爲None,那麼__init__
根本不會被調用。 - 舉個例子
class A(object):
def __new__(cls):
print("__new__方法被執行")
return super().__new__(cls)
def __init__(self):
print("__init__方法被執行")
a = A()
結果如下:
__new__方法被執行
__init__方法被執行
2、實例銷燬: __del__
__del__(self)
:當一個實例被銷燬的時候調用的方法
3、類語法糖: __call__
__call__(self[, args...])
允許一個類的實例像函數一樣被調用: x(a, b)
調用 x.__call__(a, b)
3.1、演示代碼
class A(object):
def __init__(self, name):
self.name = name
print(f"{name}被創造出來了了")
def __call__(self, *args, **kwargs):
"""允許一個類的實例像函數一樣被調用"""
print(f"{self.name}打了個響指,一共{len(args)}名超級英雄消失,他們分別是{'、'.join(args)}等。")
def __del__(self):
"""程序結束(銷燬)後,會自動執行"""
print(f"{self.name}被銷燬了")
if __name__ == '__main__':
a = A("滅霸")
a("蜘蛛俠", "幻世", "黑豹", "奇異博士")
結果如下:
滅霸被創造出來了了
滅霸打了個響指,一共4名超級英雄消失,他們分別是蜘蛛俠、幻世、黑豹、奇異博士等。
滅霸被銷燬了
二、調用系統方法
語法 | 說明 | 符號 |
---|---|---|
__len__(self) |
定義當被 len 調用時的行爲 |
len() |
__repr__(self) |
定義當被 repr 調用時的行爲 |
repr() |
__str__(self) |
定義當被 str 調用時的行爲 |
str() |
__bytes__(self) |
定義當被 bytes 調用時的行爲 |
bytes() |
__hash__(self) |
定義當被 hash 調用時的行爲 |
hash() |
__bool__(self) |
定義當被 bool 調用時的行爲 |
bool() |
__format__(self, format_spec) |
定義當被 format 調用時的行爲 |
format() |
1、 __repr__
和 __str__
的區別
-
當一個類中沒有使用
__repr__
和__str__
這兩個魔法函數,print()
直接打印實例會返回類名和內存地址 -
__repr__
和__str__
這兩個方法都是用於顯示的,__str__
是面向用戶的,而__repr__
面向程序員。 -
打印操作會首先嚐試
__str__
,它通常應該返回一個友好的顯示。 -
在交互環境下,
__repr__
會生效,而__str__
不會起作用。直接執行實例變量名即可打印,無需print()
。
2、參考代碼
class A(object):
"""對照組:沒有使用__str__,打印結果爲類型名及其內存地址"""
def __init__(self, info):
self.info = info
class B(object):
"""標準組:使用__str__了,打印結果爲__str__魔法函數的返回值"""
def __init__(self, info):
self.info = info
def __str__(self):
return self.info + f"我是{__class__.__name__}類,我用了__str__的魔法函數,真香!"
if __name__ == '__main__':
demo_message = "真氪金定律:程序員的能力與髮量成反比!快拔頭髮吧!"
a = A(demo_message)
b = B(demo_message)
print(a)
print(b)
顯示結果如下:
<__main__.A object at 0x1097bf438>
真氪金定律:程序員的能力與髮量成反比!快拔頭髮吧!我是B類,我用了__str__的魔法函數,真香!
2.1、什麼是交互環境
交互環境的典型特點是寫完一行,執行一行。非交互環境要一次執行所有程序
- 直接在Terminal中啓動python,就是交互環境
- Jupyter Notebook和Lab都是交互環境
- Pycharm中的python Console是交互環境
- Anaconda Spyder中提供的Console也是交互環境
2.2、在交互環境中 __repr__
和 __str__
代碼如下:
[外鏈圖片轉存中…(img-2LbfSCdz-1572167290870)]
- 可以看出,5行中直接執行
a
因爲定義了__repr__
,在交互環境下是可以正確顯示的; - 而在7行中,直接執行
b
是無法正確顯示的。
三、有關屬性
語法 | 說明 |
---|---|
__getattr__(self, name) |
定義當用戶試圖獲取一個不存在的屬性時的行爲 |
__getattribute__(self, name) |
定義當該類的屬性被訪問時的行爲 |
__setattr__(self, name, value) |
定義當一個屬性被設置時的行爲 |
__delattr__(self, name) |
定義當一個屬性被刪除時的行爲 |
__dir__(self) |
定義當 dir() 被調用時的行爲 |
__get__(self, instance, owner) |
定義當描述符的值被取得時的行爲 |
__set__(self, instance, value) |
定義當描述符的值被改變時的行爲 |
__delete__(self, instance) |
定義當描述符的值被刪除時的行爲 |
1、 __getattr__
和 __setattr__
存在的問題
__getattr__
和__setattr__
這兩個方法__init__
中變量賦值事,就會默認被創建。- 舉個例子:傳統賦值方式是
self.a="a"
,__getattr__
和__setattr__
就會默認創建出來。 - 所以當在
__getattr__
中使用getattr()
或者self.xxx
, 程序會陷入無限遞歸。 - 防止遞歸解決方式有兩種,但原理相同。使用超類賦值或者通過超類進行訪問
- 超類賦值(新式類):super().setattr(“xxx”, xxx)
- 超類訪問(新式類):super().getattr(“xxx”)
2、演示代碼
代碼功能:僞造一個類,使其具有與
dict
類型變量相同的功能;
同時,該類還具備通過’點’來訪問dict的方法
class A(object):
"""僞造字典"""
def __init__(self, **kwargs):
super().__setattr__("dict_like", kwargs) # 超類賦值,防止遞歸
def __getattr__(self, name):
"""可以調用dict類的系統方法"""
print("__getattr__被執行:", end="")
if name in self.dict_like:
print(f"\t訪問{name}變量", end="")
return self.dict_like[name]
else:
print(f"\t訪問{name}方法", end="")
return getattr(self.dict_like, name)
def __setattr__(self, key, value):
"""可以通過'點'的方式,設置字典內容"""
print("__setattr__被執行:", end="")
self.dict_like[key] = value
def __delattr__(self, item):
print("__delattr__被執行:", end="")
del self.dict_like[item]
def __len__(self):
print("__len__被執行:", end="")
return len(self.dict_like)
def __str__(self):
print("__str__被執行:", end="")
return str(self.dict_like)
if __name__ == '__main__':
a = A(a=1, b=2, c=3, d=4, e=5, f=6, g=7) # 實例化
print(a) # 打印
print(len(a)) # 獲取長度
a.h = 8 # 字典賦值
print(a) # 打印賦值結果
print("\t|\t訪問b元素:", a.b) # 訪問b元素
print(list(a.items())) # 調用dict的系統方法
del a.f # 刪除字典元素
print(a) # 打印刪除後的結果
結果如下:
__str__被執行:{'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6, 'g': 7}
__len__被執行:7
__setattr__被執行:__str__被執行:{'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6, 'g': 7, 'h': 8}
__getattr__被執行:訪問b變量 | 訪問b元素: 2
__getattr__被執行:訪問items方法[('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5), ('f', 6), ('g', 7), ('h', 8)]
__delattr__被執行:__str__被執行:{'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'g': 7, 'h': 8}
四、比較操作符
語法 | 說明 | 符號 |
---|---|---|
__lt__(self, other) |
定義小於號的行爲 | x < y |
__le__(self, other) |
定義小於等於號的行爲 | x <= y |
__eq__(self, other) |
定義等於號的行爲 | x == y |
__ne__(self, other) |
定義不等號的行爲 | x != y |
__gt__(self, other) |
定義大於號的行爲 | x > y |
__ge__(self, other) |
定義大於等於號的行爲 | x >= y |
五、算數運算符
可以讓類使用算數預算符進行操作
1、標準運算符
說明 | 代碼 | 符號 | 反運算 | 增強運算 |
---|---|---|---|---|
加法 | __add__(self, other) |
+ | √ | += |
減法 | __sub__(self, other) |
- | √ | -= |
乘法 | __mul__(self, other) |
* | √ | *= |
除法 | __truediv__(self, other) |
/ | √ | /= |
整除 | __floordiv__(self, other) |
// | √ | //= |
模運算 | __mod__(self, other) |
% | √ | %= |
divmod操作 | __divmod__(self, other) |
divmod() | √ | 無 |
次方 | __pow__(self, other[, modulo]) |
power()或 ** | √ | **= |
左位移 | __lshift__(self, other) |
<< | √ | <<= |
右位移 | __rshift__(self, other) |
>> | √ | >>= |
按位與 | __and__(self, other) |
& | √ | &= |
按位異或 | __xor__(self, other) |
^ | √ | ^= |
按位或 | __or__(self, other) |
| |
√ | |= |
2、反運算
- 在標準運算符的前面加上“r”就是反運算
- 假設
a
和b
是兩個實例,就a而言,a+b
爲標準預算,因爲a
在前邊;b+a
就是反運算,因爲a
在後面。 - 如果
a
想使用反運算,就需要定義__radd__
,
3、增量賦值運算
- 在標準運算符的前面加上“i”就是增量賦值運算
4、代碼實例
4.1、3種運算符的區別
class A(object):
def __add__(self, other):
print("A:加法的標準運算")
def __radd__(self, other):
print("A:加法的反運算")
def __iadd__(self, other):
print("A:加法的增強賦值運算")
class B:
pass
a=A()
b=B()
a+b
b+a
a+=b
輸出結果:
A:加法的標準運算
A:加法的反運算
A:加法的增強賦值運算
4.2、僞造list類,使其具備運算符功能
class A(object):
"""僞造list,通過運算符號,完成增減數據"""
def __init__(self, *args):
self.list_like = list(args) # args默認爲元組類型,需要轉換成爲list
def __add__(self, other):
print("__add__被執行:", end="")
self.list_like.append(other)
return self
def __radd__(self, other):
print("__radd__被執行:", end="")
self.list_like = [other] + self.list_like
return self
def __iadd__(self, other):
print("__iadd__被執行:", end="")
self.list_like.append(other)
return self
def __mul__(self, other):
print("__mul__被執行:", end="")
self.list_like = list(map(lambda x: x * other, self.list_like))
return self
def __len__(self):
return len(self.list_like)
def __str__(self):
return str(self.list_like)
if __name__ == '__main__':
a = A(1, 2, 3, 5, 6, 7)
a = a + 10 # 向list中最後一位添加數據10
print(a)
a = 0 + a # 向list中第一位添加數據0
print(a)
a += 100 # 向list中最後一位添加數據100
print(a)
a = a * 6 # 向list中每一位乘以6
print(a)
輸出結果:
__add__被執行:[1, 2, 3, 5, 6, 7, 10]
__radd__被執行:[0, 1, 2, 3, 5, 6, 7, 10]
__iadd__被執行:[0, 1, 2, 3, 5, 6, 7, 10, 100]
__mul__被執行:[0, 6, 12, 18, 30, 36, 42, 60, 600]
六、一元操作符
語法 | 說明 | 符號 |
---|---|---|
__neg__(self) |
定義正號的行爲 | -x |
__pos__(self) |
定義負號的行爲 | +x |
__abs__(self) |
定義絕對值的行爲 | abs() |
__invert__(self) |
定義按位求反的行爲 | ~x |
1、示例代碼
class A(object):
"""僞造list,具備算法運算符功能"""
def __init__(self, *args):
self.list_like = list(args) # args默認爲元組類型,需要轉換成爲list
def __neg__(self):
print("__neg__被執行:", end="")
self.list_like = list(map(lambda x: -x, self.list_like))
return self
def __abs__(self):
print("__abs__被執行:", end="")
self.list_like = list(map(lambda x: abs(x), self.list_like))
return self
def __invert__(self):
print("__invert__被執行:", end="")
self.list_like = list(map(lambda x: ~x, self.list_like))
return self
def __len__(self):
return len(self.list_like)
def __str__(self):
return str(self.list_like)
if __name__ == '__main__':
a = A(5, 3, 16, 14, 60, 3)
a = -a # 對list中每一位數據取負值
print(a)
a = abs(a) # 對list中每一位數據絕對值
print(a)
a = ~a # 對list中每一位數據取反
print(a)
輸出結果
__neg__被執行:[-5, -3, -16, -14, -60, -3]
__abs__被執行:[5, 3, 16, 14, 60, 3]
__invert__被執行:[-6, -4, -17, -15, -61, -4]
七、類型轉換
語法 | 說明 |
---|---|
__complex__(self) |
定義當被 complex() 調用時的行爲(需要返回恰當的值) |
__int__(self) |
定義當被 int() 調用時的行爲(需要返回恰當的值) |
__float__(self) |
定義當被 float() 調用時的行爲(需要返回恰當的值) |
__round__(self[, n]) |
定義當被 round() 調用時的行爲(需要返回恰當的值) |
__index__(self) |
當對象是被應用在切片表達式中時,實現整形強制轉換;如果你定義了一個可能在切片時用到的定製的數值型, 你應該定義 index。如果 index 被定義,則 int 也需要被定義,且返回相同的值 |
八、上下文管理(with 語句)
演示代碼如下:
with open("./1.txt","r",encoding="utf-8") as f:
data=f.readlines()
語法 | 說明 |
---|---|
__enter__ |
1. 定義當使用 with 語句時的初始化行爲; 2. __enter__ 的返回值被 with 語句的目標或者 as 後的名字綁定 |
__exit__ |
1. 定義當一個代碼塊被執行或者終止後上下文管理器應該做什麼 2. 一般被用來處理異常,清除工作或者做一些代碼塊執行完畢之後的日常工作 |
九、容器類型
語法 | 說明 |
---|---|
__len__(self) |
定義當被 len() 調用時的行爲(返回容器中元素的個數) |
__getitem__(self, key) |
定義獲取容器中指定元素的行爲,相當於 self[key] |
__setitem__(self, key, value) |
定義設置容器中指定元素的行爲,相當於 self[key] = value |
__delitem__(self, key) |
定義刪除容器中指定元素的行爲,相當於 del self[key] |
__iter__(self) |
定義當迭代容器中的元素的行爲 |
__reversed__(self) |
定義當被 reversed() 調用時的行爲 |
__contains__(self, item) |
定義當使用成員測試運算符( in 或 not in )時的行爲 |
1、 示例代碼
class Avenger(object):
"""復仇者聯盟"""
def __init__(self, **kwargs):
self.members = kwargs
self.names = list(kwargs.keys())
self.index = 0
def __getitem__(self, item):
fighting_num = self.members[item]
print("__getitem__被執行:", f"正在讀取復仇者【{item}】的信息,其戰鬥力爲{fighting_num}")
return fighting_num
def __setitem__(self, key, value):
print("__setitem__被執行:", f"新增復仇者【{key}】,其戰鬥力爲{value}")
self.members["key"] = value
def __delitem__(self, key):
print("__delitem__被執行:", f"復仇者【{key}】已陣亡!")
del self.members["key"]
def __iter__(self):
print("__iter__被執行")
return self
def __next__(self):
if self.index == len(self.names) - 1:
raise StopIteration
else:
self.index += 1
return self.names[self.index]
def __gt__(self, other):
print("__gt__被執行", f"戰鬥力大於60的復仇者:{other}")
members = filter(lambda x: x[-1] > other, self.members.items())
return list(members)
def __len__(self):
return len(self.members)
def __str__(self):
power_info = map(lambda x: "{0}的戰鬥力爲 {1}".format(*x), self.members.items())
return "\n".join(power_info)
if __name__ == '__main__':
a = Avenger(鋼鐵俠=106, 雷神=110, 浩克=120, 奇異博士=90, 蜘蛛俠=80, 美國隊長=50, 幻世=60)
print(len(a))
print(f"======== 雷神的戰鬥力:{a['雷神']} ========")
a["古魯姆"] = 15.6
del a["幻世"]
for i in a:
print(i)
print(a > 60)
結果如下:
7
__getitem__被執行: 正在讀取復仇者【雷神】的信息,其戰鬥力爲110
======== 雷神的戰鬥力:110 ========
__setitem__被執行: 新增復仇者【古魯姆】,其戰鬥力爲15.6
__delitem__被執行: 復仇者【幻世】已陣亡!
__iter__被執行
雷神
浩克
奇異博士
蜘蛛俠
美國隊長
幻世
__gt__被執行 戰鬥力大於60的復仇者:60
[('鋼鐵俠', 106), ('雷神', 110), ('浩克', 120), ('奇異博士', 90), ('蜘蛛俠', 80)]