背景
今天在B站學習“零基礎入門學習 Python”中的第45節“魔法方法:屬性訪問”,這也是我們組織的 Python基礎刻意練習活動 的學習任務,其中有這樣的一個題目。
練習要求:
- 寫一個矩形類,默認有寬和高兩個屬性。
- 如果爲一個叫
square
的屬性賦值賦值,那麼說明這是一個正方形,值就是正方形的邊長,此時寬和高都應該等於邊長。
技術分析
我們先來看看有關於屬性的四個魔法方法:
__getattr__(self, name)
: 定義當用戶試圖獲取一個不存在的屬性時的行爲。__getattribute__(self, name)
:定義當該類的屬性被訪問時的行爲(先調用該方法,查看是否存在該屬性,若不存在,接着去調用__getattr__
)。__setattr__(self, name, value)
:定義當一個屬性被設置時的行爲。__delattr__(self, name)
:定義當一個屬性被刪除時的行爲。
class Test:
def __getattr__(self, name):
print('__getattr__')
def __getattribute__(self, name):
print('__getattribute__')
def __setattr__(self, name, value):
print('__setattr__')
def __delattr__(self, name):
print('__delattr__')
t = Test()
t.x
# __getattribute__
如上述代碼所示,x
並不是Test
實例對象t
的一個屬性,首先去調用 __getattribute__()
方法,得知該屬性並不屬於該實例對象。但是,按照常理,t.x
應該打印 __getattribute__
和__getattr__
,但實際情況並非如此,爲什麼呢?
實例對象屬性尋找的順序如下:
① 首先訪問 __getattribute__()
魔法方法(隱含默認調用,無論何種情況,均會調用此方法)。
② 接着,去t.__dict__
中查找是否具備該屬性。
③ 若在 t.__dict__
中找不到對應的屬性, 則去t.__class__.__dict__
中尋找。
④ 若在實例的類中也找不到該屬性,則去父類中尋找,即 t.__class__.__bases__.__dict__
中尋找
⑤ 若以上均無法找到,則會調用 __getattr__
方法,執行內部的命令(若未重載 __getattr__
方法,則直接報錯:AttributeError
)
以上幾個流程,即完成了屬性的尋找。
但是,以上的說法,並不能解釋爲什麼執行 t.x
時,不打印 __getattr__
啊?
問題就出在了步驟的第④步,因爲,一旦重載了 __getattribute__()
方法,如果找不到屬性,則必須要手動加入第④步,否則無法進入到 第⑤步 (__getattr__
)的。
驗證一下以上說法是否正確:
方法一:採用 object
(所有類的基類)
class Test:
def __getattr__(self, name):
print('__getattr__')
def __getattribute__(self, name):
print('__getattribute__')
object.__getattribute__(self, name)
def __setattr__(self, name, value):
print('__setattr__')
def __delattr__(self, name):
print('__delattr__')
t = Test()
t.x
# __getattribute__
# __getattr__
方法二:採用 super()
方法
class Test:
def __getattr__(self, name):
print('__getattr__')
def __getattribute__(self, name):
print('__getattribute__')
super().__getattribute__(name)
def __setattr__(self, name, value):
print('__setattr__')
def __delattr__(self, name):
print('__delattr__')
t = Test()
t.x
# __getattribute__
# __getattr__
以上介紹完畢,那麼 __setattr__
和 __delattr__
方法相對簡單多了:
class Test:
def __getattr__(self, name):
print('__getattr__')
def __getattribute__(self, name):
print('__getattribute__')
object.__getattribute__(self, name)
def __setattr__(self, name, value):
print('__setattr__')
def __delattr__(self, name):
print('__delattr__')
t = Test()
t.x = 1
# __setattr__
del t.x
# __delattr__
對了,再補充一點哈!
class Test:
def __init__(self):
self.count = 0
def __setattr__(self, name, value):
print('__setattr__')
self.count += 1
t = Test()
# AttributeError: 'Test' object has no attribute 'count'
看報錯信息很容易明白,這是因爲:
① __init__()
時,給內部屬性 self.count
進行了賦值;
② 賦值默認調用 __setattr__()
方法
③ 當調用 __setattr__()
方法時,首先打印 __setattr__
字符串,而後執行 self.cout += 1
操作
④ 當執行 self.cout + 1
操作時,將會去尋找 count
這個屬性,然而,由於此時 __init__
尚未完成,並不存在 count
這個屬性,因此導致 AttributeError
錯誤。
那麼該如何更改呢?可以這樣的:
class Test:
def __init__(self):
self.count = 0
def __setattr__(self, name, value):
print('__setattr__')
super().__setattr__(name, value + 1)
t = Test()
print(t.count)
# __setattr__
# 1
以上代碼雖然解決了報錯的問題,深入體會一下,你會發現,採用此方法只是給 基類object
增加了一個屬性 count
,而並不是實例的屬性,因此,以上這種寫法避免使用。
另外,再次將代碼改進一下,如下:
class Test:
def __setattr__(self, name, value):
self.name = value
t = Test()
t.x = 'lsgo'
# RecursionError: maximum recursion depth exceeded
當我們給 t.x
賦值時,調用了 __setattr__()
方法,進入該方法。該方法中,又來了一次賦值(self.name = value
),又會去調用 __setattr__()
方法,持續這個死循環。
所以,我們只好改變上述的問題了:
class Test:
def __setattr__(self, name, value):
print('__setattr__() been called')
super().__setattr__(name, value)
t = Test()
t.x = 'lsgo'
# __setattr__() been called
print(t.x)
# lsgo
代碼實現
上面詳細介紹了關於屬性的四個魔法方法,下面我們來看實現要求的具體代碼:
class Rectangle:
def __init__(self, width=0, height=0):
self.width = width
self.height = height
def __setattr__(self, key, value):
if key == 'square':
self.width = value
self.height = value
else:
super().__setattr__(key, value)
def getArea(self):
return float(self.width) * float(self.height)
r = Rectangle(4, 5)
print(r.getArea()) # 20.0
r.square = 10
print(r.__dict__) # {'width': 10, 'height': 10}
print(r.getArea()) # 100.0
總結
魔法方法是 Python 面向對象編程中最核心的內容,需要花費一定的精力才能將其掌握,參加 Python基礎刻意練習的小夥伴們加油! See You!
參考文獻:
- https://www.bilibili.com/video/av4050443/?p=46
- https://www.runoob.com/python3/python3-tutorial.html
- https://www.cnblogs.com/Jimmy1988/p/6804095.html
相關圖文: