被管理的属性

描述符必须赋给一个类属性而不是实例属性,否则它将无法工作。

__init__构造函数中的属性赋值也会触发__setattr__方法,这个方法捕获所有属性赋值,即便是类自身之中的那些。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
4个访问器技术:
在这里插入图片描述
和property一样,描述符也管理一个单个的、指定的属性。

当描述符的instance参数为None的时候,该描述符就知道它正被类名直接访问。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Python 中与属性访问相关的一些魔法方法:

1.getattr(self, name):当默认属性访问抛出 AttributeError 异常(可能是 getattribute 无法找到对应实例的属性而抛出 AttributeError 异常,或者实例本身无法在继承树上找到对应的实例,再或者是描述符对象的 get 对该属性抛出 AttributeError异常导致)时被调用。该方法应当返回一个值或者抛出一个 AttributeError 异常。

注:如果按正常的属性搜索机制找到了属性,getattr 不会被调用。

  1. getattribute(self, name):该方法会被无条件调用, 无论属性存不存在。如果同时定义了 getattr 方法,除非 getattribute 显示调用它或者抛出一个 AttributeError 异常,否则不会被调用。该方法应当返回一个值或者抛出一个 AttributeError 异常。为了避免潜在的无限递归调用,方法的实现应当永远返回基类的同名方法调用,比如object.getattribute(self, name)。

  2. getattribute(self, name) 被对象的.操作触发;getitem(self, key) 被对象的[]操作触发。

官方定义:python描述符是一个“绑定行为”的对象属性,在描述符协议中,它可以通过方法重写属性的访问。这些方法有 get(), set(), 和__delete__()。如果这些方法中的任何一个被定义在一个对象中,这个对象就是一个描述符。

描述符是实现了特定协议的类,这个协议包括 getset 和__delete__ 方法。

描述符魔法方法:get(), set(), delete()

方法的原型为:

  •   __get__(self, instance, owner)
    
    • set__(self, instance, value)__
      
    • __del__(self, instance)
      

当Python解释器发现实例对象的字典中,有与描述符同名的属性时,描述符优先,会覆盖掉实例属性。数据描述符会覆盖掉对象实例属性字典__dict__,非数据描述符属性会被对象实例对应属性覆盖。

描述符分数据描述符与非数据描述符(只定义了 get() 方法,而没有定义 set(), delete() 方法)

当使用实例对象访问属性时,都会调用__getattribute__内建函数,__getattribute__查找属性的优先级如下:

1、类属性

2、数据描述符

3、实例属性

4、非数据描述符

5、getattr()

Python中对象的属性具有 “层次性”,属性在哪个对象上定义,便会出现在哪个对象的__dict__中。

getattribute__是实例对象查找属性或方法的入口。实例对象访问属性或方法时都需要调用到__getattribute,之后才会根据一定的规则在各个__dict__中查找相应的属性值或方法对象,若没有找到则会调用__getattr__

实例对象的__setattr__方法可以定义属性的赋值行为,不管属性是否存在。当属性存在时,它会改变其值;当属性不存在时,它会添加一个对象属性信息到对象的__dict__中,然而这并不改变类的属性。

搜索链(或者优先链)的顺序大概是这样的:数据描述符>实体属性(存储在实体的_dict_中)>非数据描述符。

覆盖性**-**非覆盖性描述符调用优先级:

非覆盖型的获取属性的优先级链是,getattribute->找__dict__->找描述符, 这条链的规则给了"缓存属性"理论支持

有__get__方法的覆盖型的获取属性的优先级链是,getattribute->找描述符.

没有__get__方法的覆盖型的获取属性优先级链是

在没有对属性赋值时是__getattribute__->找描述符(返回描述符对象本身),对属性赋值了之后是__getattribute__->找__dict__->找描述符.

在 Python 中,静态方法staticmethod() 和 类方法classmethod() 以非数据描述符(non-data descriptor)的方式进行实现,因此,对象实例可以覆盖重定义这些方法。

property() 方法以数据描述符(data descriptor)进行实现,因此,对象实例无法重写property行为。

当 数据描述符作为类属性 时,在对象上使用该属性(a.x,x为 data descriptor)时,赋值时会调用数据描述符的__set__方法,取值时会调用数据描述符的__get__方法。对象的属性字典obj.__dict__不起作用。当在 类对象(A.data_desc)上使用数据描述符属性时,由于该属性本身就是类对象所有,因此设置属性时,不会调用数据描述符的__set__方法,而是直接设置到类的__dict__中;而取值流程与对象的流程一致,只是数据描述符__get__的参数instance变为None。

对象访问非数据描述符时,先调用对象的__dict__,没有才调用描述符的__get__方法。对于非数据描述符,类对象使用时直接调用其__get__方法。

vars方法用于查看对象的属性,等价于对象的__dict__内容。

类属性可以使用对象和类访问,多个实例对象共享一个类变量。但是只有类才能修改。

对象中属性访问的默认行为就是在对象的字典中get、set或delete相应的属性。例如,a.x的查找顺序是从 a.dict[‘x’] 到 type(a).dict[‘x’],然后继续在type(a)除元类(metaclass)外的基类中查找。如果要查找的值是定义了任意Descriptor方法的对象,那么Python会调用Descriptor方法来覆盖属性查找的默认行为。查找的优先级顺序取决于定义了哪些Descriptor方法。

Data Descriptor和Non-data Descriptor的不同体现在关于实例字典条目的覆盖和计算顺序上。如果实例字典中包含了与Data Descriptor同名的属性,那么Data Descriptor优先。如果实例字典中包含了与Non-data Descriptor同名的属性,实例字典优先。

调用的细节取决于obj是对象还是类。

对于对象来说,其机制是object.getattribute()将b.x转换为type(b).dict[‘x’].get(b, type(b))。其实现的优先级链是:Data Descriptor优先级高于实例变量(instance variables),实例变量优先级高于Non-data Descriptor,而 getattr() 的优先级是最低的。完整的c代码实现在Objects/object.c的PyObject_GenericGetAttr()函数中。

对于类来说,其机制是type.getattribute()将B.x转换为B.dict[‘x’].get(None, B)。

需要记住的重要几点:

Descriptor是通过__getattribute__()方法来调用的

覆写__getattribute__()可以阻止Descriptor的自动调用

object.getattribute()和type.getattribute()调用__get__()的方式不同

Data Descriptor总是覆盖实例字典

Non-data Descriptor可能会被实例字典覆盖

为了支持方法调用,函数有__get__()方法,可以在属性访问时绑定方法。这意味着所有的函数都是Non-data Descriptor,根据调用方是对象或类来返回绑定或非绑定方法。

静态方法返回没有任何变化的原函数。调用c.f或C.f相当于直接查找object.getattribute(c, “f”)或object.getattribute(C, “f”)。因此,函数通过对象或类来调用是等价的。

当函数仅需要类引用并且不关心任何内部数据时,类方法是非常有用的。类方法的一个用途就是代替类构造函数来创建对象。

如前所述,Python 存取属性的方式特别不对等。通过实例读取属性时,通常返回的是实例中定义的属性;但是,如果实例中没有指定的属性,那么会获取类属性。而为实例中的属性赋值时,通常会在实例中创建属性,根本不影响类。

实现 set 方法的描述符属于覆盖型描述符,因为虽然描述符是类属性,但是实现 set 方法的话,会覆盖对实例属性的赋值操作。

通常,覆盖型描述符既会实现 set 方法,也会实现 get 方法,不过也可以只实现 set 方法。此时,只有写操作由描述符处理。通过实例读取描述符会返回描述符对象本身,因为没有处理读操作的 get 方法。如果直接通过实例的 __dict__属性创建同名实例属性,以后再设置那个属性时,仍会由 set 方法插手接管,但是读取那个属性的话,就会直接从实例中返回新赋予的值,而不会返回描述符对象。也就是说,实例属性会遮盖描述符,不过只有读操作是如此。

不管描述符是不是覆盖型,为类属性赋值都能覆盖描述符。

在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值