描述符介绍

本文深入探讨了Python中的描述符,这是一种具有绑定行为的对象属性,控制了属性访问。描述符是Python类模型的重要组成部分,用于实现属性、方法、静态方法、类方法等。描述符协议包括__get__、__set__和__delete__方法。通过定义这些方法,可以创建数据描述符和非数据描述符。文中还通过示例解释了描述符的工作原理和调用机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在 Python 教程的前两章中,我们学习了如何使用Python 属性,甚至如何实现自定义属性类。在本章中,您将了解描述符的详细信息。

描述符是在 2.2 版中引入 Python 的。那个时候《Python2.2 的新特性》中提到:“新的类模型背后的一个大思想是,使用描述符来描述对象属性的 API 已经被形式化了。描述符指定了一个属性的值,说明是否它是一个方法或一个字段。使用描述符 API,静态方法和类方法以及更多奇特的构造成为可能。”

描述符是具有“绑定行为”的对象属性,其属性访问已被描述符协议中的方法覆盖。这些方法是__get__()__set__()__delete__()

如果为对象定义了这些方法中的任何一个,则称其为描述符。

它们的目的在于为程序员提供向类添加托管属性的能力。描述符被引入以__dict__通过上述方法从对象的字典中获取、设置或删除属性。访问类属性将启动查找链。

让我们仔细看看正在发生的事情。假设我们有一个对象obj:如果我们尝试访问一个属性(或属性)会发生ap什么?“访问”属性意味着“获取”值,因此该属性用于例如打印函数或表达式内部。无论是obj和类属于type(obj)包含字典属性__dict__。这种情况如下图所示:

属性和属性的查找链

obj.ap有一个以 开头的查找链obj.__dict__['ap'],即检查是否obj.ap是字典的键obj.__dict__['ap']

属性和属性的查找链,检查 obj.__dict__ 中是否有 ```ap```

如果ap不是 的键obj.__dict__,它将尝试查找type(obj).__dict__['ap']

属性和属性的查找链,检查 type(obj).__dict__ 中是否有 ```ap```

如果obj也没有包含在这个字典中,它会继续检查type(ap)排除元类的基类。

我们在一个例子中证明了这一点:

 A : 
    ca_A  =  "A 的类属性" 
    def  __init__ ( self ): 
        self ia_A  =  "A 实例的实例属性" 
class  B ( A ): 
    ca_B  =  "B 的类属性" 
    def  __init__ ( self ): 
        super () __init__ ()
        自我ia_B  =  "A 实例的实例属性" 
x  =  B () 
print ( x . ia_B)
打印( x . ca_B )
打印( x . ia_A )
打印( x . ca_A )

输出:

实例的实例属性
B的类属性
实例的实例属性
A的类属性

如果我们调用,print(x.non_existing)我们会得到以下异常:

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-4-119192d61d5e> in <module>
----> 1 print(x.non_existing)
AttributeError: 'B' object has no attribute 'non_existing'

如果查找的值是定义描述符方法之一的对象,则 Python 可能会覆盖默认行为并改为调用描述符方法。这在优先链中发生的位置取决于定义了哪些描述符方法。

描述符提供了一个强大的通用协议,它是属性、方法、静态方法、类方法和 super() 背后的底层机制。需要描述符来实现 2.2 版中引入的所谓新样式类。“新样式类”是当今的默认设置。

描述符协议

通用描述符协议由三种方法组成:

descr.__get__(self, obj, type=None) -> value
descr.__set__(self, obj, value) -> None
descr.__delete__(self, obj) -> None

如果您定义了这些方法中的一个或多个,您将创建一个描述符。我们区分数据描述符和非数据描述符:

非数据描述符

如果我们只定义 __get__() 方法,我们就创建了一个非数据描述符,主要用于方法。

数据描述符

如果一个对象定义了 __set__() 或 __delete__(),它被认为是一个数据描述符。要创建只读数据描述符,请使用 __set__() 定义 __get__() 和 __set__() 并在调用时引发 AttributeError 。使用异常引发占位符定义 __set__() 方法足以使其成为数据描述符。

现在我们终于来到了下面代码中的简单描述符示例:

class  SimpleDescriptor ( object ): 
    """
    一个可以设置和返回值的简单数据描述符
    """ 
    def  __init__ ( self ,  initval = None ): 
        print ( "__init__ of SimpleDecorator call with initval:" ,  initval ) 
        self . __set__ ( self ,  initval ) 
    def  __get__ ( self ,  instance ,  owner ): 
        print ( instance ,  owner) 
        print ( 'Getting (Retrieving) self.val:' ,  self . val ) 
        return  self . val 
    def  __set__ ( self ,  instance ,  value ): 
        print ( 'Setting self.val to ' ,  value ) 
        self val  =  value 
class  MyClass ( object ): 
    x  =  SimpleDescriptor ( "green" ) 
m  =  MyClass ()
打印(米x )
米x  =  "黄色"
打印( m . x )

输出:

用 initval 调用 SimpleDecorator 的 __init__:green
将 self.val 设置为绿色
<__main__.MyClass 对象在 0x7efe93ff4a20> <class '__main__.MyClass'>
获取(检索)self.val:绿色
绿色
将 self.val 设置为黄色
<__main__.MyClass 对象在 0x7efe93ff4a20> <class '__main__.MyClass'>
获取(检索)self.val:黄色
黄色的

第三个参数owner__get__永远是所有者类,并为用户提供了一个选项,做一些与被用来调用描述符的类。通常,即如果描述符是通过对象调用的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值