zoukankan      html  css  js  c++  java
  • Python中的Descriptor描述符

    1、什么是描述符?

    描述符是Python新式类的关键点之一,它为对象属性提供强大的API,你可以认为描述符是表示对象属性的一个代理。当需要属性时,可根据你遇到的情况,通过描述符进行访问他(摘自Python核心编程)。

    2、类属性和实例属性

    2.1 属性:__dict__

    作用:字典类型,存放本对象的属性,key(键)即为属性名,value(值)即为属性的值,形式为{attr_key : attr_value}。

    __dict__是对象的默认属性,所以每个类对象和实例化对象都有这个属性。

    对象属性的访问顺序:

    ​ (1)实例对象/类对象的属于描述符的属性

    ​ (2)实例属性

    ​ (3)类属性

    ​ (3)父类属性

    ​ (4)__getattr__()方法

    2.2 实例属性的调用顺序

    作用:查找类对象或者实例对象的属性(也就是用于获取对象的__dict__属性中的值)

    这三个魔法方法的调用顺序如下:

    如果 obj = Clz(), 那么obj.attr 顺序如下:

    ​ (1)如果“attr”出现在Clz__dict__中, 且attrdata descriptor, 那么调用其__get__方法

    ​ (2)如果“attr”出现在obj__dict__中, 那么直接返回 obj.__dict__['attr']

    ​ (3)如果“attr”出现在Clz__dict__

    ​ (3.1)如果attr是non-data descriptor,那么调用其__get__方法

    ​ (3.2)否则,返回__dict__['attr']

    ​ (4)如果Clz__getattr__方法,调用__getattr__方法

    ​ (5)抛出AttributeError

    实际上还是上面那个调用顺序。只是结合描述符进行了一些补充关于描述符的补充

    3 Descriptor 的定义

    描述符本质上是一个类属性,实现描述符的类被称为描述符类。

    其中只实现了__set__()方法的被当做方法描述符,或者是非数据描述符。

    那些同时实现了__set__()__get__()方法的类被称作数据描述符。

    而魔法方法__get__(), __set__(), __delete__() 就用于定义和调用类属性 __dict__

    __get__(self, object, type)                  # 用于得到一个属性的值
    __set__(self, obj, val)                      # 用于为一个属性赋值
    __delete__(self, obj)                        # 删除某个属性时被调用,但很少用到
    

    3.1 描述符的定义和调用初体验

    # 描述符类的定义
    class MyDescriptor(object):
        def __init__(self, value):
            self.value = value
    
        # 描述符value的访问
        def __get__(self, instance, owner):
            return self.value
    
        # 描述符value的定义
        def __set__(self, instance, value):
            self.value = value
    
    
    class MyClass(object):
    
      mydescriptor = MyDescriptor(5)
        # 在MyClass类中创建一个描述符mydescriptor,重申一下,这是一个类属性。
        # 同时可以看到,mydescriptor不仅仅是MyClass类的一个类属性,同时还是MyDescriptor的一个实例对象。
        # 这样就将一个类的类属性定义成了另一个类的实例对象。
    
        
    if __name__ == '__main__':
        print (MyClass.mydescriptor)  # 输出为 5
    

    发现访问 MyClass 的 mydescriptor 属性时,调用了描述符的__get__()方法,访问了描述符类的实例属性value.

    这就达到了描述符的作用:可以改变了类对象属性的访问。

    调用原理:对于类属性描述符,如果解析器发现属性x是一个描述符的话,在内部通过type.__getattribute__()(访问属性时无条件调用,最先调用),它能把Class.x转换成Class.__dict__[‘x’].__get__(None, Class)来访问

    4、描述符与普通属性的区别

    上面简单说了几个定义,接下来我们来解决一些实际使用中的细节问题。

    4.1 首先我们看一下普通的属性调用

    class Test(object):
        cls_val = 1
        def __init__(self, ins_val):
            self.ins_val = ins_val
             
    >>> t=Test(10)
    
    >>> Test.__dict__
    {'__module__': '__main__', 
     '__init__': <function Test.__init__ at 0x03975150>, 
     '__dict__': <attribute '__dict__' of 'Test' objects>, 
     '__weakref__': <attribute '__weakref__' of 'Test' objects>, 
     '__doc__': None,
     'cls_val': 1, }
    
    >>> t.__dict__
    {'ins_val': 10}
    
    # 没有写在__init__中的类属性在实例化时是不会在实例的__doc__中的。 
    # 注意,这里并不是说实例t没有属性cls_val。t.cls_val依旧为1。
    
    # ------------------------------------- -------------------------------------
    
    >>> t.cls_val = 20
    
    >>> Test.__dict__
    {'__module__': '__main__', 
     '__init__': <function Test.__init__ at 0x035D5150>, 
     '__dict__': <attribute '__dict__' of 'Test' objects>, 
     '__weakref__': <attribute '__weakref__' of 'Test' objects>, 
     '__doc__': None,
     'cls_val': 1, }
    
    >>> t.__dict__
    {'ins_val': 10, 'cls_val': 20}
    
    # 更改实例t的属性cls_val,并不影响类Test的类属性cls_val
    
    # ------------------------------------- -------------------------------------
        
    >>> Test.cls_val = 30
    
    >>> Test.__dict__
    {'__module__': '__main__', 
     '__init__': <function Test.__init__ at 0x035D5150>, 
     '__dict__': <attribute '__dict__' of 'Test' objects>, 
     '__weakref__': <attribute '__weakref__' of 'Test' objects>, 
     '__doc__': None,
     'cls_val': 30, }
    
    >>> t.__dict__
    {'ins_val': 10, 'cls_val': 20}
    
    # 更改了类Test的属性cls_val的值,由于实例在修改类属性之前生成,所以修改类的属性不会影响到实例的属性
    

    以上这段代码证明:

    在实例化结束之后,类属性和实例属性互不影响。

    4.2 下面我们看看__get__()方法的调用过程

    class Desc(object):
        def __init__(self, value):
            self.value = value
        
        def __get__(self, instance, owner):
            print("...__get__...")
            print("self : 		", self)
            print("instance : 	", instance)
            print("owner : 	", owner)
            return self.value
            
        def __set__(self, instance, value):
            print('...__set__...')
            print("self : 		", self)
            print("instance : 	", instance)
            print("value : 	", value)
            self.value = value
    
    
    class TestDesc(object):
        desc = Desc(666)
    
    
    
    
    # 以下为测试代码
    testdesc = TestDesc()
    print('testdesc.desc:%s' % testdesc.desc)
    print('-' * 40)
    print('TestDesc.desc:%s' % TestDesc.desc)
    
    print()
    print('=' * 40)
    print()
    
    testdesc.desc = 888
    print('-' * 40)
    print('testdesc.desc:%s' % testdesc.desc)
    print('-' * 40)
    print('TestDesc.desc:%s' % TestDesc.desc)
    
    
    # 以下为输出结果
    
    ...__get__...
    self : 		 <__main__.Desc object at 0x038C2290>
    instance : 	 <__main__.TestDesc object at 0x038C22F0>
    owner : 	 <class '__main__.TestDesc'>
    testdesc.desc:666
    ----------------------------------------
    ...__get__...
    self : 		 <__main__.Desc object at 0x038C2290>
    instance : 	 None
    owner : 	 <class '__main__.TestDesc'>
    TestDesc.desc:666
    
    ========================================
    
    ...__set__...
    self : 		 <__main__.Desc object at 0x038C2290>
    instance : 	 <__main__.TestDesc object at 0x038C22F0>
    value : 	 888
    ----------------------------------------
    ...__get__...
    self : 		 <__main__.Desc object at 0x038C2290>
    instance : 	 <__main__.TestDesc object at 0x038C22F0>
    owner : 	 <class '__main__.TestDesc'>
    testdesc.desc:888
    ----------------------------------------
    ...__get__...
    self : 		 <__main__.Desc object at 0x038C2290>
    instance : 	 None
    owner : 	 <class '__main__.TestDesc'>
    TestDesc.desc:888
    

    以上代码说明:

    1. 调用实例属性和调用类属性调到的是同一个对象,实际上他们都是由描述符类调用的。

    2. 不管是类对象的类属性还是实例对象的实例属性,其实际属性都是描述符的实例对象。

    3. 被描述的类属性在被实例化时是被实例对象继承的,示例中testdesc.desc和TestDesc.desc有相同的值,而且修改实例属性testdesc.desc会影响到类属性TestDesc.desc。

    4.3 描述符是不能定义成实例属性的

    # coding=utf-8
    class Descriptor(object):
        def __init__(self, value):
            self.value = value
    
        def __get__(self, instance, owner):
            print ("访问属性")
            return self.value
    
        def __set__(self, instance, value):
            print ("设置属性值")
            self.value = value
    
    
    class TestDesc(object):
        classdesc = Descriptor(888)
    
        def __init__(self):
            self.insdesc = Descriptor(666)
    
    
    # 以下为测试代码
    testdesc = TestDesc()
    print(TestDesc.classdesc)
    print(testdesc.classdesc)
    print(testdesc.insdesc)
    
    
    # 以下为输出结果
    访问属性
    888
    访问属性
    888
    <__main__.Descriptor object at 0x0000025041A64940>
    

    可以看到,实例对象testdesc的 实例属性insdesc 并没有调用__get__()方法,只是说他是一个Descriptor对象。

    这是因为当访问实例描述符对象时,obj.__getattribute__()会将myclass.desc转换为type(myclass).__dict__['desc'].__get__(myclass, type(myclass)),即到类属性中去寻找desc,并调用他的__get__()方法。而Myclass类中没有desc属性,所以无法访调用到__get__方法.
    描述符是一个类属性,必须定义在类的层次上, 而不能单纯的定义为对象属性。

    4. python的property方法

    通过使用 property(),可以轻松地为任意属性创建可用的描述符。

    property内建函数有四个参数:property(fget=None, fset=None, fdel=None, doc=None)

    这四个参数都接受函数类型

    class PropertyDesc(object):
        def __init__(self):
            self.__name = ''
    
        def fget(self):
            print ("Getting: %s" % self.__name)
            return self.__name
        
        def fset(self, value):
            self.__name = value
            print ("Setting: %s" % value)
    
        def fdel(self):        
            print ("Deleting: %s" % self.__name)
            del self.__name
        
        name = property(fget, fset, fdel, "I'm the property.")
    
    if __name__ == '__main__':
        pro = PropertyDesc()
        pro.name = "hellokitty"
        print(pro.name)
        del pro.name
    
    # 以下为输出结果
    Setting: hellokitty
    Getting: hellokitty
    hellokitty
    Deleting: hellokitty
    

    当然也可以使用装饰器的方式实现以上内容:

    class PropertyDesc(object):
        def __init__(self):
            self._name = ''
    
        @property
        def name(self):
            print ("Getting: %s" % self._name)
            return self._name
    
        @name.setter
        def name(self, value):
            print ("Setting: %s" % value)
            self._name = value
    
        @name.deleter
        def name(self):
            print ("Deleting: %s" %self._name)
            del self._name
    
    if __name__ == '__main__':
        pro = PropertyDesc()
        pro.name = "hello world"
        print(pro.name)
        del pro.name
    
    
    # 以下为输出内容
    Setting: hello world
    Getting: hello world
    hello world
    Deleting: hello world
    
  • 相关阅读:
    集中式(SVN)和分布式(Git)版本控制系统的简单比较
    Mac 提示安装包已损坏
    React 获取 url 参数 —— this.props.match
    编写一个 Chrome 浏览器扩展程序
    webpack 配置学习笔记
    Python 进阶学习笔记
    Python 入门学习笔记
    (转)Unity3d各种坑
    unity3d 网页游戏客户端工程构建方案
    (转)在Unity3D的网络游戏中实现资源动态加载
  • 原文地址:https://www.cnblogs.com/sablier/p/14532588.html
Copyright © 2011-2022 走看看