python高级编程——描述符Descriptor详解(中篇)——python对象的属性访问优先级与属性的控制与访问)

本文声明:python的描述符descriptor,这是属于python高级编程的一些概念和实现方法,可能有很多的小伙伴还并没有用到过,但是在Python的面试过程中有可能会出现,究竟什么是python描述符,有什么作用,使用有什么意义,它的诞生背景是什么,很少有文章专门介绍这一块,有的文章介绍的太过粗浅,以至于看过之后依然不能够理解描述符的本质。鉴于此,我寻思着出一期专门讲解python描述符的系列文章,跟前面的python装饰器系列文章一样,因为涉及到的内容偏多,本文依然是分为上、中、下、补充篇四个系列部分进行讲解,本文是第二篇——中篇,介绍Python的对象的属性访问优先级与对象属性的简单控制。

一、属性的控制与访问——属性控制三剑客

前面已经讲过了__getattribute__方法的使用,我们说它是属性和方法访问的入口,也就是说,访问一个属性和方法的时候必然实现访问__getattribute__魔术方法,但是如果我尝试访问一个根本不存在的属性或者是方法的时候,会显示错误,那么如果我想要访问一个不存在的属性和方法,而且不报错该怎么处理呢?这就是接下来要讲的第一个属性访问控制方法

1、__getattr__(self, name)

__getattr__可以用来在当用户试图访问一个根本不存在(或者暂时不存在)的属性时,来定义类的行为。如下代码所示

class Animal(object):
    run = '我会跑'
    def die(self):
        return '我会死'
class Dog(Animal):
    color='Blue'
    def __init__(self, name,age):
        self.name=name
        self.age = age
    def __getattr__(self, key):
        if key=='height':
            return 70
        elif key=='weight':
            return 30
        elif key=='sleep':
            return '我喜欢睡觉'
        else:
            return '还没有定义该属性或者是方法哦!'   #这个地方也可以自定义的抛出某一种异常哦!
       
    def sound(self):
        return "汪汪汪"

dog=Dog('泰迪',4)
print(dog.height,end='\n------------------------------\n') #调用不存在的属性height
print(dog.weight,end='\n------------------------------\n') #调用不存在的属性weight
print(dog.sleep,end='\n------------------------------\n')  ##调用不存在的属性sleep
print(dog.laugh,end='\n------------------------------\n')  #调用不存在的属性laugh

运行结果为:

70
------------------------------
30
------------------------------
我喜欢睡觉
------------------------------
还没有定义该属性或者是方法哦!
------------------------------

关于__getattr__ 和 __getattribute__的详细区别,后面再进行统一总结,通过这里的实例我们发现,当我们试图访问不存在的属性的时候,可以通过定义__getattr__方法来控制它的行为。

__getattr__ 和 __getattribute__的详细区别总结:

(1)__getattribute__称之为“属性、方法拦截器”,不管是属性还是方法,第一步就是先访问__getattribute__;而__getattr__仅仅针对的是属性,不针对方法,即访问未存在的方法的时候依然还是会报错。

(2)__getattribute__针对的是访问已经存在的(属性和方法);__getattr__针对的是访问未存在的(属性)。

(3)__getattribute__和__getattr__虽然针对每一个访问的key,一定要有对应的返回值(参见前文),但是返回的东西却不是一样的,即__getattribute__返回父类的__getattribute__函数,而__getattr__返回我希望为未知属性设置的那个值或者是异常信息。

2、__setattr__(self, name, value)

__setattr__方法允许定义为某个属性赋值的时候所发生行为,不管这个属性存在与否,都可以对任意属性的任何变化都定义自己的规则。

class Animal(object):
    run = '我会跑'
    def die(self):
        return '我会死'
class Dog(Animal):
    color='Blue'
    def __init__(self, name,age):
        self.name=name
        self.age = age
    def __setattr__(self, key,value):
        print('我被赋值啦!')
        super(Dog,self).__setattr__(key,value)
       
    def sound(self):
        return "汪汪汪"

dog=Dog('泰迪',4)
dog.age=6
dog.color='Yellow'
dog.run='我跑的很快'
dog.height=70
print(dog.age,end='\n------------------------------\n') #修改已经存在的实例属性age
print(dog.color,end='\n------------------------------\n')  #修改已经存在的类属性color
print(dog.run,end='\n------------------------------\n')  #修改已经存在的父类的类属性run
print(dog.height,end='\n------------------------------\n') #修改根本就不存在的属性height

运行结果为:

我被赋值啦!
我被赋值啦!
我被赋值啦!
我被赋值啦!
我被赋值啦!
我被赋值啦!
6
------------------------------
Yellow
------------------------------
我跑的很快
------------------------------
70
------------------------------

细心地小伙伴一定会发现,明明只有4个地方修改了属性,为什么会打印出6个“我被赋值啦”语句,实际上,最开始的两个是在定义dog对象的时候就打印了的,也就是说,在构造dog对象的时候,为属性name和age初始化赋值的时候,也是调用了__setattr__方法的。

总结:

只要是属性被修改或者是赋值,不管这个属性是实例属性、类属性、父类的类属性;亦或者是已经存在的属性、不存在的属性,只要是修改和赋值,都会调用到__steattr__方法。

补充:

关于__setattr__有一点需要说明,使用它时必须小心,不能写成类似self.name = “张三”这样的形式,因为这样的赋值语句会调用__setattr__方法,这样会让其陷入无限递归,参见前文的__getattribute__方法出现无限递归的情况,二者是一样的;

3、__delattr__(self, name)

__delattr__用于处理删除属性时的行为。和__setattr__方法要注意无限递归的问题,重写该方法时不要有类似del self.name的写法。

class Animal(object):
    run = '我会跑'
    def die(self):
        return '我会死'
class Dog(Animal):
    color='Blue'
    def __init__(self, name,age):
        self.name=name
        self.age = age
    def __delattr__(self, key):
        print('我被删除啦!')
        super(Dog,self).__delattr__(key)
       
    def sound(self):
        return "汪汪汪"

dog=Dog('泰迪',4)
del dog.age
del dog.name

运行结果为:

我被删除啦!
我被删除啦!

注意:__delattr__只能够删除 已经存在的、实例属性,对于不存在的属性和类属性(因为它是属于类的)是不能够删除的。

本章总结:

上面讲到的三个重要的魔术方法__getattr__、__setattr__、__delattr__这三个方法实现了对属性的的灵活控制,称之为“属性控制三剑客”。它们控制着属性的访问、修改、删除等操作行为,其中最需要注意的是是防止出现在这几个方法内部有显式调用self.property,因为这会出现无限递归问题,导致系统崩溃(虽然python解释器对递归的次数有所限制,比如限制递归200次,可能不同版本有所区别,但尽量还是别出现无限递归的好)。但是这三者对属性的控制也不是没有缺点的,接下来就讨论一下她们的缺点。

4、属性控制三剑客的缺点

访问、赋值(修改)、删除、属性的这三个基本操作是最为常见的,即便我们在定义某个类,或者是某一系列类的时候,我们完全不定义“属性控制三剑客”,我们依然可以对属性进行这三个操作,所以从这个层面上来说,似乎“属性控制三剑客”显得那么的多余,其实也不是,它们可以定义这三个基本操作在发生的时候发生其他的行为啊,这没有错,但是这不是特别专业。因为__getattribute____getattr____setattr____delattr__等方法用来实现属性查找、设置、删除操作只能实现属性操作的一般逻辑,所谓一般逻辑就是大家都一样,你这个属性和我这个属性没啥太大区别,定制的不够个性化,如果我要专门针对每一个属性都定制成完全不一样的属性,使用这种一般的逻辑,自然不是特别好了。最好的方法是抽离出来我需要特别个性化定制的属性,我专门用一个类去描述这属性,控制这个属性所绑定的行为、访问、修改、删除等操作,也就是后一篇文章要讲的“描述符descriptor”。

如果是这一段文字不是特别懂,没有关系,和后面的描述符结合起来看一定可以看得懂的。

 

下一篇内容:python高级编程——描述符Descriptor详解(下篇)——python描述符三剑客详解

  • 4
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值