查看原文
其他

Python高级编程——描述符Descriptor超详细讲解(中篇之属性控制)

草yang年华 机器学习与python集中营 2021-09-10

送你小心心记得关注我哦!!

进入正文

全文摘要

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

上篇请参考:

全文目录

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

01 __getattr__(self, name)

02 __setattr__(self, name, value)

03  __delattr__(self,name)

04 属性控制三剑客的缺点


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



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


01

__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__返回我希望为未知属性设置的那个值或者是异常信息。


02

__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__方法出现无限递归的情况,二者是一样的


03

__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次,可能不同版本有所区别,但尽量还是别出现无限递归的好)。但是这三者对属性的控制也不是没有缺点的,接下来就讨论一下她们的缺点。


04

属性控制三剑客的缺点 


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


推 荐 阅 读

Python高级编程——描述符Descriptor超详细讲解(上篇之属性优先级)
Python高级编程——装饰器Decorator超详细讲解(下篇之python闭包详解)
python开发者必备,史上最详细的 ipython 教程——第二篇

详解语言模型NGram及困惑度Perplexity


2018/12/10

Monday

小伙伴们,如果你耐心的看完了关于python描述符的系列文章(上篇),是不是有所收获呢?是不是对python复杂的属性访问搞得有点模糊不清?没关系,后面还有更加模糊的,不过没关系,当你静下心来,认真看完本系列文章,我相信一定会大有收获的。欢迎小伙伴们继续关注和支持。如果你有需要,就添加我的公众号哦,里面分享有海量资源,包含各类数据、教程等,后面会有更多面经、资料、数据集等各类干货等着大家哦,重要的是全都是免费、无套路分享,有兴趣的小伙伴请持续关注!










: . Video Mini Program Like ,轻点两下取消赞 Wow ,轻点两下取消在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存