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

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

复制代码

怎么用

知道了它们的不同处之后, 那我们该怎么用呢? 什么时候用哪个呢???

  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)

转载于:https://juejin.im/post/5be18f3d6fb9a049f9120a38

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
`__getattr__` 和 `__getattribute__` 都是 Python 中的魔术方法,用于在类中处理属性的访问。两者有一些区别: - `__getattr__` 方法在访问一个不存在的属性时被调用。如果实现了 `__getattr__` 方法,那么在访问一个不存在的属性时,Python 解释器会自动调用该方法并将属性名作为参数传递给它。该方法应该返回一个值来代替访问的属性。如果该方法无法提供属性的值,那么 Python 解释器会引发 `AttributeError` 异常。 - `__getattribute__` 方法在访问一个属性时被调用。该方法在访问任何属性时都会被调用,而不仅仅是不存在的属性。如果实现了 `__getattribute__` 方法,那么在访问属性时,Python 解释器会自动调用该方法并将属性名作为参数传递给它。该方法应该返回属性的值。如果该方法无法提供属性的值,那么 Python 解释器会引发 `AttributeError` 异常。 下面是一个简单的例子,演示了 `__getattr__` 和 `__getattribute__` 方法的用法: ```python class MyClass: def __init__(self): self.data = {} def __getattr__(self, name): if name in self.data: return self.data[name] else: raise AttributeError(f"'MyClass' object has no attribute '{name}'") def __getattribute__(self, name): if name == 'data': return object.__getattribute__(self, 'data') elif name in self.data: return self.data[name] else: raise AttributeError(f"'MyClass' object has no attribute '{name}'") ``` 在上述代码中,我们定义了一个名为 `MyClass` 的类,该类包含一个字典属性 `data`。我们分别实现了 `__getattr__` 和 `__getattribute__` 方法。在 `__getattr__` 方法中,我们检查所访问的属性是否存在于字典中。如果是,我们返回字典中的值;否则,我们引发一个 `AttributeError` 异常。在 `__getattribute__` 方法中,我们首先检查所访问的属性是否为 `data`。如果是,我们返回对象的 `data` 属性。否则,我们检查所访问的属性是否存在于字典中。如果是,我们返回字典中的值;否则,我们引发一个 `AttributeError` 异常。 下面是如何使用 `MyClass` 类: ```python my_obj = MyClass() my_obj.data['a'] = 1 my_obj.data['b'] = 2 print(my_obj.a) # 1 print(my_obj.b) # 2 print(my_obj.c) # AttributeError: 'MyClass' object has no attribute 'c' ``` 在上述示例中,我们创建了一个 `MyClass` 对象,并使用 `__getattr__` 和 `__getattribute__` 方法来访问对象的属性。我们首先向对象的 `data` 字典中添加两个键值对。然后,我们尝试访问这些键值对,以及不存在的键值对。在访问存在的键值对时,`__getattr__` 和 `__getattribute__` 方法都会返回正确的值。在访问不存在的键值对时,`__getattr__` 方法会引发 `AttributeError` 异常,而 `__getattribute__` 方法会引发一个相同的异常。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值