描述符介绍

在 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']

描述符介绍

如果ap不是 的键obj.__dict__,它将尝试查找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__永远是所有者类,并为用户提供了一个选项,做一些与被用来调用描述符的类。通常,即如果描述符是通过对象调用的obj,则可以通过调用来推断对象类型type(obj)。如果描述符是通过类调用的,情况就不同了。在这种情况下,None除非给出第三个参数,否则不可能访问该类。第二个参数instance是访问属性的实例,或者None是通过所有者访问属性的时间。

让我们看看__dict__实例和类的字典:

'x' 是上一个类中的一个类属性。您可能会问自己,我们是否也可以在__init__方法中使用这种机制来定义实例属性。这不可能。方法__get__(),__set__()__delete__()仅当包含该方法的类的实例(所谓的描述符类)出现在所有者类中时才适用(描述符必须在所有者的类字典中或在其一个的类字典中)父母)。在上面的示例中,属性“x”位于__dict__所有者类 MyClass 的所有者中,如下所示:

打印(米。__dict__ )
打印(MyClass的。__dict__ )
打印(SimpleDescriptor 。__dict__ )

输出:

{}
{'__module__': '__main__', 'x': <__main__.SimpleDescriptor object at 0x7efe93ff49e8>, '__dict__': <attribute '__dict__' of 'MyClass' objects>, '__weakref__': <attribute '__weakref__' of 'MyClass' '对象>,'__doc__':无}
{'__module__': '__main__', '__doc__': '\n 一个可以设置和返回值的简单数据描述符\n ', '__init__': <function SimpleDescriptor.__init__ at 0x7efe93fc8d08>, '__get__': <function SimpleDescriptor .__get__ at 0x7efe93fc8840>, '__set__': <function SimpleDescriptor.__set__ at 0x7efe93fc88c8>, '__dict__': <attribute '__dict__' of 'SimpleDescriptor' objects>, '__weaktribute objects __' of <weakSimpleDescriptor __': }
打印( MyClass . __dict__ [ 'x' ])

输出:

<__main__.SimpleDescriptor 对象在 0x7efe93ff49e8>

可以通过其方法名称直接调用描述符,例如d.__get__(obj)

或者,更常见的是在属性访问时自动调用描述符。例如,在 的字典中obj.d查找。如果定义了方法,则根据下面列出的优先级规则调用。d__dict__objd__get__()d.__get__(obj)

如果obj是对象或类,则有所不同:

  • 对于对象,控制调用的方法是在object.__getattribute__()其中转换b.x为调用type(b).__dict__['x'].__get__(b, type(b))。该实现通过优先级链进行工作,该优先级链赋予数据描述符高于实例变量的优先级,实例变量优先于非数据描述符的优先级,并为__getattr__()提供的情况分配最低优先级。

  • 对于类,相应的方法在type类中,即将type.__getattribute__()Bx 转换为B.__dict__['x'].__get__(None, B)

__getattribute__不是用 Python 实现的,而是用 C 实现的。 下面的 Python 代码是在 Python 中的逻辑模拟。我们可以看到描述符被__getattribute__实现调用。

def  __getattribute__ ( self ,  key ): 
    “在 Objects/typeobject.c 中模拟 type_getattro()” 
    v  =  type __getattribute__ ( self ,  key )
    如果 hasattr ( v ,  '__get__' ):
        返回 v 。__get__ ( None ,  self )
    返回 v 
m 。__getattribute__ ( "x" )

输出:

<__main__.MyClass 对象在 0x7efe93ff4a20> <class '__main__.MyClass'>
获取(检索)self.val:黄色

返回的对象super()也有__getattribute__()调用描述符的自定义方法。属性查找紧随其后super(B, obj).m搜索obj.__class__.__mro__基类,然后返回。如果不是描述符,则原样返回。如果不在字典中,则使用恢复搜索。ABA.__dict__['m'].__get__(obj, B)mmobject.__getattribute__()

上面的细节表明描述符的机制嵌入在__getattribute__()object、type 和 super() 的方法中。当类从对象派生或具有提供类似功能的元类时,类会继承此机制。这也意味着可以通过覆盖__getattribute__().

from  weakref  import  WeakKeyDictionary 
class  Voter : 
    required_age  =  18  # 在德国
    def  __init__ ( self ): 
        self . age  =  WeakKeyDictionary () 
    def  __get__ ( self ,  instance_obj ,  objtype ):
        返回 self 。年龄。get ( instance_obj ) 
    def  __set__ ( self ,  instance ,  new_age ):
        如果 new_age  <  Voter 。required_age :
            味精 =  ' {name}的不老足以在德国投票'
            加薪 异常(MSG 。格式(名称=实例。名称))
        的自我。age [ instance ]  =  new_age 
        print ( ' {name} can vote in Germany' . format ( 
            name = instance . name )) 
    def __delete__ ( self ,  instance ): 
        del  self 。年龄[实例]
 :
    voter_age  =  Voter () 
    def  __init__ ( self ,  name ,  age ): 
        self 。姓名 = 姓名
        自我。voter_age  = 年龄
p1  =  Person ( 'Ben' ,  23 ) 
p2  =  Person ( 'Emilia',  22 ) 
p2 。选民年龄

输出:

本可以在德国投票
艾米利亚可以在德国投票

property() 类的纯示例实现(python.org 的描述符

 属性:
    “在 Objects/descrobject.c 中模拟 PyProperty_Type()” 
    def  __init__ ( self ,  fget = None ,  fset = None ,  fdel = None ,  doc = None ): 
        self 。fget  =  fget 
        self 。fset  =  fset 
        self 。FDEL  =  FDEL
        如果 文档  fget   无:
            DOC =  fget 。__doc__
        自我。__doc__  =  DOC 
    DEF  __get__ (自, OBJ , OBJTYPE =无):
        如果 OBJ  无:
            返回如果 自我。fget  is  None : 
            raise  AttributeError ( "unreadable attribute" ) 
        return  self 。fget ( obj ) 
    def  __set__ ( self , obj ,  value ):
        如果 self 。fset  None :
            引发 AttributeError ( "can't set attribute" ) 
        self 。fset ( obj ,  value ) 
    def  __delete__ ( self ,  obj ): 
        if  self . fdel  None :
            引发 AttributeError ( "can't delete attribute" ) 
        self 。fdel ( obj ) 
    def 吸气剂(自, fget ):
        返回 类型(自)(fget , 自我。FSET , 自我。FDEL , 自我。__doc__ )
    DEF 设定器(自, FSET ):
        返回 类型(自)(自我。fget , FSET , 自我。FDEL ,  self . __doc__ ) 
    def 删除器( self ,  fdel ):
        返回 类型( self )( self . fget ,  self . fset ,  fdel ,  self . __doc__ )

一个使用我们的 Property 实现的简单类。

 Adef  __init__ ( self ,  prop ): 
        self 。prop  =  prop 
    @Property 
    def  prop ( self ): 
        print ( "现在将返回属性'prop':" ) 
        return  self 。__prop 
    @prop 。setter 
    def  prop ( self ,  prop ): 
        print ( "prop will be set" ) 
        self . __prop  = 道具

使用我们的 A 类:

print ( "用值'Python'初始化属性'prop'" ) 
x  =  A ( "Python" ) 
print ( "值是:" ,  x . prop ) 
print ( "将属性'prop'重新分配给'Python描述符'" ) 
x 。丙 =  “Python的描述符”
打印(“的值是:” , X 。道具)

输出:

使用值 'Python' 初始化属性 'prop'
道具将被设置
现在将返回属性“prop”:
值为:Python
将属性“prop”重新分配给“Python 描述符”
道具将被设置
现在将返回属性“prop”:
值为:Python 描述符

让我们用一些打印函数让我们的 Property 实现更健谈,看看发生了什么:

 属性:
    “在 Objects/descrobject.c 中模拟 PyProperty_Type()” 
    def  __init__ ( self ,  fget = None ,  fset = None ,  fdel = None ,  doc = None ): 
        print ( " \n __init__ of Property 调用:" )
        打印( "fget="  +  str ( fget )  +  " fset="  +  str ( fset )  + \
              " fdel="  +  str ( fdel )  +  " doc="  +  str ( doc )) 
        self 。fget  =  fget 
        self 。fset  =  fset 
        self 。FDEL  =  FDEL
        如果 文档  fget   无:
            DOC  =  fget 。__doc__
        自我。__doc__  =  doc 
    def  __get__ ( self ,  obj,  objtype = None ): 
        print ( " \n Property.__get__ has been called!" ) 
        if  obj  is  None : 
            return  self 
        if  self 。fget  is  None : 
            raise  AttributeError ( "unreadable attribute" ) 
        return  self 。fget ( obj ) 
    def  __set__ ( self ,  obj ,  value ):
        打印( " \n物业.__ set__被称为“! ),
        如果 自我。FSET  无:
            提高 AttributeError的(”不能设置属性“ )
        的自我。FSET (OBJ , 值)
    高清 __delete__ (自我, OBJ ):
        打印(” \ n属性。 !__delete__被称为” )
        ,如果 自我。FDEL  无:
            提高 AttributeError的(‘不能删除属性’ )
        自我。fdel ( obj ) 
    def  getter ( self ,  fget ): 
        print ( " \n Property.getter has been called!" ) 
        return  type ( self )( fget ,  self . fset ,  self . fdel ,  self . __doc__ ) 
    def  setter ( self ,  fset ): 
        print ( " \n Property.setter 已被调用!" )
        返回 类型(个体经营)(自我。fget , FSET , 自我。FDEL , 自我。__doc__ )
    DEF 缺失者(个体经营, FDEL ):
        打印(“ \ n Property.deleter被称为!” )
        的返回 类型(个体经营)(自我。fget 、 self . fset 、 fdel 、 self . __doc__ )
 A : 
    def  __init__ ( self ,  prop ): 
        self 。prop  =  prop 
    @Property 
    def  prop ( self ): 
        """ 这将是属性 """ 
        print ( "The Property 'prop' will be returned now:" ) 
        return  self的文档字符串。__prop 
    @prop 。setter 
    def  prop ( self ,  prop ): 
        print ( "prop will be set" ) 
        self . __prop  = prop 
    def  prop2_getter ( self ):
        返回 self 。__prop2 
    def  prop2_setter ( self ,  prop2 ): 
        self 。__prop2  =  PROP2 
    PROP2  = 属性(prop2_getter , prop2_setter )
打印(“初始化属性'道具'同值'的Python'” )
X  = 甲(“Python的” )
的打印(“的值是:” , X 。丙)
打印( "将属性 'prop' 重新分配给 'Python 描述符'" ) 
x . 丙 =  “Python的描述符”
打印(“的值是:” , X 。道具)
打印(甲。道具。吸气剂(X ))
DEF  new_prop_setter (自, 丙):
    如果 丙== “foo”的:
        自我。__prop  =  "foobar" 
    else : 
        self 。__prop  = 道具
A 。道具。二传手

输出:

__init__ 的属性调用:
fget=<function A.prop at 0x7efe93fff840> fset=None fdel=None doc=None
Property.setter 已被调用!
__init__ 的属性调用:
fget=<function A.prop at 0x7efe93fff840> fset=<function A.prop at 0x7efe9370f840> fdel=None doc= 这将是属性的文档字符串 
__init__ 的属性调用:
fget=<function A.prop2_getter at 0x7efe93715048> fset=<function A.prop2_setter at 0x7efe93715158> fdel=None doc=None
使用值 'Python' 初始化属性 'prop'
Property.__set__ 已被调用!
道具将被设置
Property.__get__ 已被调用!
现在将返回属性“prop”:
值为:Python
将属性“prop”重新分配给“Python 描述符”
Property.__set__ 已被调用!
道具将被设置
Property.__get__ 已被调用!
现在将返回属性“prop”:
值为:Python 描述符
Property.__get__ 已被调用!
Property.getter 已被调用!
__init__ 的属性调用:
fget=<__main__.A object at 0x7efe9371b080> fset=<function A.prop at 0x7efe9370f840> fdel=None doc= 这将是属性的文档字符串 
<__main__.Property 对象在 0x7efe93ff4358>
Property.__get__ 已被调用!
 机器人def  __init__ ( self ,  name = "Marvin" ,  city = "Freiburg" ): 
        self 。姓名 = 姓名
        自我。city  =  city 
    @Property 
    def  name ( self ):
        返回 self 。__name 
    @name 。setter 
    def  name ( self ,  name ): 
        if  name  ==  "hello" : 
            self. __name  =  "hi" 
        else : 
            self . __name  =  name 
x  =  Robot ( "Marvin" )
打印( x . name ) 
x . name  =  "Eddie"
打印( x . name )

输出:

__init__ 的属性调用:
fget=<function Robot.name at 0x7efe93715ea0> fset=None fdel=None doc=None
Property.setter 已被调用!
__init__ 的属性调用:
fget=<function Robot.name at 0x7efe93715ea0> fset=<function Robot.name at 0x7efe93715400> fdel=None doc=None
Property.__set__ 已被调用!
Property.__get__ 已被调用!
马文
Property.__set__ 已被调用!
Property.__get__ 已被调用!
埃迪
 Adef  a ( func ): 
        def  helper ( self ,  x ): 
            return  4  *  func ( self ,  x ) 
        return  helper 
    @a 
    def  b ( self ,  x ): 
        return  x  +  1 
a  =  A () 
a 。乙( 4 )

输出:

20

很多人问,是否可以在运行时自动创建描述符。正如我们在以下示例中所示,这是可能的。另一方面,这个例子不是很有用,因为 getter 和 setter 没有真正的功能:

class  DynPropertyClass ( object ): 
    def  add_property ( self ,  attribute ): 
        """ 向类中添加一个属性 """         
        def  get_attribute ( self ): 
            """ 将检索属性 'attribute' 的值 """ 
            return  getattr ( self ,  "_"  +  type ( x ) . __name__  +  "__"  +  attribute ) 
        def  set_attribute ( self , 值):
            """ 将检索属性 'attribute' 的值 """ 
            #setter = lambda self, value: self.setProperty(attribute, value) 
            setattr ( self ,  "_"  +  type ( x ) . __name__  +  "__"  +  attribute ,  value ) 
        #构造property 属性并将其添加到类
        setattr ( type ( self ),  attribute ,  property ( fget = get_attribute ,  
                                                fset = set_attribute , 
                                                文档=“自动生成的方法” )) 
x  =  DynPropertyClass () 
x 。add_property ( 'name' ) 
x 。add_property ( 'city' ) 
x 。姓名 =  "亨利" 
x 。姓名
x 。city  =  "Hamburg"
打印( x . name ,  x . city )
打印( x . __dict__ )

输出:

亨利·汉堡
{'_DynPropertyClass__name':'亨利','_DynPropertyClass__city':'汉堡'}
上一篇:Solon Web 开发,九、跨域处理


下一篇:VUE中子组件改变来自父组件数据的两种方式