Python 中有三個看上去非常相似的魔法方法: __getattribute__
, __getattr__
, __getitem___
, 就是前面這仨哥們兒了.
不同之處
首先來看看 __ getattribute__
和 __getattr__
, 這倆在一定程度上有先後呼叫的關係. 簡單來說, 在用.
運算子獲取某個例項的屬性值的時候, Python 直譯器會首先呼叫__getattribute__
, 如果該例項中有需要獲取的屬性值, 就返回屬性值, 如果沒有, 則會丟擲 AttributeError
.
class Foo:
def __init__(self, a):
self.a = a
def __getattribute__(self, key):
print('I\'m in __getattribute__ func')
return super(Foo, self).__getattribute__(key)
foo = Foo(3)
print(foo.a) # 訪問存在的屬性
print(foo.b) # 訪問不存在的屬性
# 執行結果如下:
I'm in __getattribute__ func
3
I'm in __getattribute__ func
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-29-1f8f708fdf11> in <module>()
16 foo = Foo(3)
17 print(foo.a) # 訪問存在的屬性
---> 18 print(foo.b) # 訪問不存在的屬性
<ipython-input-29-1f8f708fdf11> in __getattribute__(self, key)
5 def __getattribute__(self, key):
6 print('I\'m in __getattribute__ func')
----> 7 return super(Foo, self).__getattribute__(key)
8
AttributeError: 'Foo' object has no attribute 'b'
複製程式碼
但是, 如果你在定義類的時候實現了 __getattr__
方法, 那麼在 __getattribute__
丟擲 AttributeError
後, 就會執行 __getattr__
. 當然, 如果 __getattribute__
獲取到了屬性值, __ getattr__
就不會被呼叫.
class Foo:
def __init__(self, a):
self.a = a
def __getattribute__(self, key):
print('I\'m in __getattribute__ func')
return super(Foo, self).__getattribute__(key)
def __getattr__(self, key):
print('I\'m in __getattr__ func')
return 0
foo = Foo(3)
print(foo.a) # 訪問存在的屬性
print(foo.b) # 訪問不存在的屬性
# 執行結果如下:
I'm in __getattribute__ func
3
I'm in __getattribute__ func
I'm in __getattr__ func
0
複製程式碼
其實我們用 getattr(instance, key)
獲取屬性值的時候, 內部呼叫其實是和 .
運算子是一樣的!
print(getattr(foo, 'a'))
print(getattr(foo, 'b'))
# 執行結果如下:
I'm in __getattribute__ func
3
I'm in __getattribute__ func
I'm in __getattr__ func
0
複製程式碼
接下來就是 __getitem__
了. 其實過載 __getitem__
實現了容器類, 也就是你可以像字典和列表一樣, 通過 instance['key']
或者 instance[index]
獲取屬性值.
class Poo:
def __init__(self, a):
self.a = a
def __getitem__(self, key):
try:
val = self.__getattribute__(key)
except AttributeError:
val = 0
return val
a = Poo(3)
print(a.a)
print(a['a'])
print(a['b'])
# 執行結果如下:
3
3
0
複製程式碼
怎麼用
知道了它們的不同處之後, 那我們該怎麼用呢? 什麼時候用哪個呢???
__getitem__
:
__getitem__
主要是用於將一個普通的類變成一個容器類, 可以通過像其他容器獲取屬性值一樣獲取自定義類的屬性值__getattr__
:
__getattr__
主要作用是定製化獲取例項中不存在的屬性後觸發的動作__getattribute__
:
__getattibute__
可以用於阻止獲取例項中的某些敏感屬性
class Count:
def __init__(self,mymin,mymax):
self.mymin=mymin
self.mymax=mymax
self.current=None
def __getattribute__(self, item):
if item.startswith('cur'):
raise AttributeError
return object.__getattribute__(self,item)
# or you can use ---return super().__getattribute__(item)
obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.current)
# 執行結果如下:
1
10
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-4-2c95d579e6a7> in <module>()
14 print(obj1.mymin)
15 print(obj1.mymax)
---> 16 print(obj1.current)
<ipython-input-4-2c95d579e6a7> in __getattribute__(self, item)
7 def __getattribute__(self, item):
8 if item.startswith('cur'):
----> 9 raise AttributeError
10 return object.__getattribute__(self,item)
11 # or you can use ---return super().__getattribute__(item)
AttributeError:
複製程式碼
需要注意的是, 在過載 __getattribute__
的時候, 為了防止無線遞迴, 我們應該呼叫基類的 __getattribute__
方法(object.__getattribute__(self, key)
或者 super().__getattribute__(key)
), 而不是直接通過 self.__dict__[key]
這種形式獲取屬性值.
最後 PO 一個檢視魔法方法的官方文件 Index: Index – _
參考: