1. 程式人生 > >魔術方法(一) __getattribute__ VS __getattr__ VS __getitem___

魔術方法(一) __getattribute__ VS __getattr__ VS __getitem___

Python 中有三個看上去非常相似的魔法方法: __getattribute__, __getattr__, __getitem___, 就是前面這仨哥們兒了.

不同之處

首先來看看 __ getattribute____getattr__, 這倆在一定程度上有先後呼叫的關係. 簡單來說, 在用. 運算子獲取某個例項的屬性值的時候, Python 直譯器會首先呼叫__getattribute__, 如果該例項中有需要獲取的屬性值, 就返回屬性值, 如果沒有, 則會丟擲 AttributeError.

image

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

複製程式碼

怎麼用

知道了它們的不同處之後, 那我們該怎麼用呢? 什麼時候用哪個呢???

  1. __getitem__:
    __getitem__ 主要是用於將一個普通的類變成一個容器類, 可以通過像其他容器獲取屬性值一樣獲取自定義類的屬性值
  2. __getattr__:
    __getattr__ 主要作用是定製化獲取例項中不存在的屬性後觸發的動作
  3. __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 – _

參考:

  1. Difference between getattr vs getattribute
  2. object.getattr(self, name)
  3. object.getattribute(self, name)
  4. object.getitem(self, key)