python中魔术方法:关于__getattr__、__setattr__和__getattribute__中的关键点

python中魔术方法:关于__getattr__、__setattr__和__getattribute__

getattr

__getattr__比较好理解~
首先明白__getattr__是什么。
**getattr()**函数会在你试图读取一个不存在的属性时,引发AttributeError异常。
下面看一段代码段。

class testclass:
    count = 0
    name = []
    def __init__(self,name,count):
        self.count += count
        self.name = name
    def __getattr__(self, item):
        print('无此项,出错')
m = testclass('wang',100)
m.addr
m.addr='上海'
print(m.name)
print(m.addr)

为了简化代码,就没有涉及__del__(self)等,比较好理解,这个就简单定义了一个类,里面有name和count两个属性,但是没有addr,这时候运行的结果如下:

无此项,出错
wang
上海

这个很好理解接下来看__getattribute__。

getattribute

getattribute() 是在查找真正要访问的属性之前就被调用了,无论该属性是否存在。可以跟__getattr__有个区别了吧(在定义层面)。
使用__getattribute__()要特别注意,因为如果你在它里面不知何故再次调用了__getattribute__(),你就会进入无穷递归。为了避免这种情况,如果你想要访问任何它需要的属性,应该总是调用祖先类的同名方法:比如super(obj, self).getattribute(attr)。
上面这话怎么理解呢?看下面这段代码:

class testclass:
    count = 0
    name = []
    def __init__(self,name,count):
        self.count += count
        self.name = name
    def __getattr__(self, item):
         print('无此项,出错')
    def __getattribute__(self, item):
         return 1
m = testclass('wang',100)
m.addr
m.addr='上海'
print(m.name)
print(m.addr)
del m.addr

大家运行上面这个代码发现惊人的结果:

1
1

对的没有看错都变成了1。
换种更直观的:

class TestMain:
    def __init__(self):
        print('TestMain:__init__')
        self.a = 1

    def __getattr__(self, item):
        print('TestMain:__getattr__')
        return 2

    def __getattribute__(self, item):
        print('TestMain:__getattribute__')
        return 3

if __name__ == '__main__':
    t = TestMain()
    print(t.a)
    print(t.b)

结果是

TestMain:__init__
TestMain:__getattribute__
3
TestMain:__getattribute__
3

这时候就可以看出来了吧,无论是访问存在的t.a还是不存在的t.b,都访问到了__getattribute__这个函数,也就是说,只要定义了这个函数,那么属性的访问,都会走到这个函数里面。

我们再看下面这段代码:

class TestMain:
    def __init__(self):
        print('TestMain:__init__')
        self.a = 1

    def __getattr__(self, item):
        print('TestMain:__getattr__')
        return 2

    def __getattribute__(self, item):
        print('TestMain:__getattribute__')
        if item == 'c':
            raise AttributeError
        return 3

if __name__ == '__main__':
    t = TestMain()
    print(t.a)
    print(t.b)
    print(t.c)

我们知道只要定义了__getattribute__函数,就肯定执行这个函数来获取属性,这次我们增加了判断如果访问c这个属性,我们抛出异常,最后的结果是:

TestMain:__init__
TestMain:__getattribute__
3
TestMain:__getattribute__
3
TestMain:__getattribute__
TestMain:__getattr__
2

也就是说,如果__getattribute__抛出了AttributeError异常,那么会继续访问__getattr__函数的。

那么有人会问,那该怎么办呢

class TestMain:
    def __init__(self):
        print('TestMain:__init__')
        self.a = 1
        self.b = 2

    def __getattr__(self, item):
        print('TestMain:__getattr__')
        return 3

    def __getattribute__(self, item):
        print('TestMain:__getattribute__')
        return super().__getattribute__(item)

if __name__ == '__main__':
    t = TestMain()
    print(t.a)
    print(t.b)
    print(t.c)
是不是就是咱们刚开始提到的,那这个运行出来结果是什么呢?

```python
TestMain:__init__
TestMain:__getattribute__
1
TestMain:__getattribute__
2
TestMain:__getattribute__
TestMain:__getattr__
3

注意这行:

def __getattribute__(self, item):
         return super().__getattribute__(item)

我们发现每次访问类的属性的时候,
都会有TestMain:__getattribute__输出,这就验证了我们刚刚说的只要定义了__getattribute__函数,就肯定执行这个函数来获取属性。
但是加了这两行后就可以正常输出各个属性的值,也可以正常映射到__getattr__方法中。

那么这两句话是什么意思的?其实就是返回父代这个类的属性的值,以防止__getattribute__来篡改属性值从而返回错误的结果。
虽然本例并没有实现继承,但是python中默认所有的类都继承自object(被称为超类可以省略的),所以就直接调用父类同名的方法就可以了!

总的来说:

1、如果定义了__getattribute__,那么无论访问什么属性,都是通过这个函数获取,包括方法,t.f()这种也是访问的这个函数,此时这个函数应该放回一个方法,如果像例子中,仍然返回一个数字,你会获得一个TypeError: ‘int’ object is not callable错误。

2、只要定义了__getattribute__方法,不管你访问一个存在的还是不存在的属性,都由这个方法返回,比如访问t.a,虽然a存在,但是只要定义了这个访问,那么就不是访问最开始的a了

3、如果__getattribute__抛出了AttributeError异常,并且定了了__getattr__函数,那么会调用__getattr__这个函数,不论这个属性到底是不是存在

4、如果想保持属性的值,则可以
return super().getattribute(item)

4、也就是说属性访问的一个大致优先级是:getattribute > getattr > dict

setattr

__setattr__可以限制或者管理对象成员的添加或修改操作。
其参数self接收当前对象,第二个参数接收设置的成员名称字符串,第三个参数接收设置的值
__setattr__同理,有了上面的基础,接下来直接看这个(就不循序渐进了,都是父类同名方法):

class TestMain:
    def __init__(self):
        print('TestMain:__init__')
        self.a = 1
        self.b = 2

    def __getattr__(self, item):
        print('TestMain:__getattr__')
        return 3

    def __getattribute__(self, item):
        print('TestMain:__getattribute__')
        return super().__getattribute__(item)
    def __setattr__(self, key, value):
        print('TestMain:__setattr__')
        super().__setattr__(key,value)

if __name__ == '__main__':
    t = TestMain()
    print(t.a)
    print(t.b)
    print(t.c)

咋们应该知道super().setattr(key,value)什么意思啦(避免输出错的属性值,__getattribute__中提到有~),来看看结果:

TestMain:__init__
TestMain:__setattr__
TestMain:__setattr__
TestMain:__getattribute__
1
TestMain:__getattribute__
2
TestMain:__getattribute__
TestMain:__getattr__
3

还没有完,因为到这里可能有的小伙伴还在懵逼状态,我们这里放慢速度,将主函数改为:

if __name__ == '__main__':
    t = TestMain()
    # print(t.a)
    # print(t.b)
    # print(t.c)

结果为

TestMain:__init__
TestMain:__setattr__
TestMain:__setattr__

没错还是两个TestMain:setattr
好了不做消融试验了,其实这里的TestMain:__setattr__出现的次数是取决于你类中定义的属性个数,这里是a,b,也就是两个,小伙伴们可以尝试一下~
这里可以理解为,只要访问属性时,这么说不准确,也就是在定义对象时包含属性的的个数就是访问__setattr__的次数。

本文到这里就结束了,希望能对这3个有个了解和初步的认识。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值