zoukankan      html  css  js  c++  java
  • python数据描述符

    Python的描述符是接触到Python核心编程中一个比较难以理解的内容,自己在学习的过程中也遇到过很多的疑惑,通过google和阅读源码,现将自己的理解和心得记录下来,也为正在为了该问题苦恼的朋友提供一个思考问题的参考,由于个人能力有限,文中如有笔误、逻辑错误甚至概念性错误,还请提出并指正。

    2、什么是描述符

          Python 2.2 引进了 Python 描述符,同时还引进了一些新的样式类,但是它们并没有得到广泛使用。Python 描述符是一种创建托管属性的方法。描述符具有诸多优点,诸如:保护属性不受修改、属性类型检查和自动更新某个依赖属性的值等。

          说的通俗一点,从表现形式来看,一个类如果实现了__get__,__set__,__del__方法(三个方法不一定要全部都实现),并且该类的实例对象通常是另一个类的类属性,那么这个类就是一个描述符。__get__,__set__,__del__的具体声明如下:

          __get__(self, instance, owner) 
          __set__(self, instance, value)
          __delete__(self, instance)

    其中:
          __get__ 用于访问属性。它返回属性的值,或者在所请求的属性不存在的情况下出现 AttributeError 异常。类似于javabean中的get。
          __set__ 将在属性分配操作中调用。不会返回任何内容。类似于javabean中的set。
          __delete__ 控制删除操作。不会返回内容。

     注意:

          只实现__get__方法的对象是非数据描述符,意味着在初始化之后它们只能被读取。而同时实现__get__和__set__的对象是数据描述符,意味着这种属性是可读写的。   

    3、为什么需要描述符

          因为Python是一个动态类型解释性语言,不像C/C++等静态编译型语言,数据类型在编译时便可以进行验证,而Python中必须添加额外的类型检查逻辑代码才能做到这一点,这就是描述符的初衷。比如,有一个测试类Test,其具有一个类属性name。

    class Test(object):
        name = None

    正常情况下,name的值(其实应该是对象,name是引用)都应该是字符串,但是因为Python是动态类型语言,即使执行Test.name = 3,解释器也不会有任何异常。当然可以想到解决办法,就是提供一个get,set方法来统一读写name,读写前添加安全验证逻辑。代码如下:

    class test(object):
        name = None
        @classmethod
        def get_name(cls):
            return cls.name
        @classmethod
        def set_name(cls,val):
            if  isinstance(val,str):
                cls.name = val
            else:
                raise TypeError("Must be an string")

    虽然以上代码勉强可以实现对属性赋值的类型检查,但是会导致类型定义的臃肿和逻辑的混乱。从OOP思想来看,只有属性自己最清楚自己的类型,而不是它所在的类,因此如果能将类型检查的逻辑根植于属性内部,那么就可以完美的解决这个问题,而描述符就是这样的利器。

          为name属性定义一个(数据)描述符类,其实现了__get__和__set__方法,代码如下:

    class name_des(object):
        def __init__(self):
            self.__name = None
        def  __get__(self, instance, owner):
            print('call __get__')
            return self.__name
        def  __set__(self, instance, value):
            print('call __set__')
            if  isinstance(value,str):
                self.__name = value
            else:
                raise TypeError("Must be an string")

    测试类如下

    class test(object):
        name = name_des()

    测试代码及输出结果如下

    >>> t = test()
    >>> t.name
    call __get__
    >>> t.name = 3
    call __set__
    Traceback (most recent call last):
      File "<pyshell#99>", line 1, in <module>
        t.name = 3
      File "<pyshell#94>", line 12, in __set__
        raise TypeError("Must be an string")
    TypeError: Must be an string
    >>> t.name = 'my name is chenyang'
    call __set__
    >>> t.name
    call __get__
    'my name is chenyang'
    >>>

    从打印的输出信息可以看到,当使用实例访问name属性(即执行t.name)时,便会调用描述符的__get__方法(注意__get__中添加的打印语句)。当使用实例对name属性进行赋值操作时(即t.name = 'my name is chenyang.'),从打印出的'call set'可以看到描述符的__set__方法被调用。熟悉Python的都知道,如果name是一个普通类属性(即不是数据描述符),那么执行t.name = 'my name is chenyang.'时,将动态产生一个实例属性,再次执行t.name读取属性时,此时读取的属性为实例属性,而不是之前的类属性(这涉及到一个属性查找优先级的问题,下文会提到)。

         至此,可以发现描述符的作用和优势,以弥补Python动态类型的缺点。

    转自https://www.cnblogs.com/chenyangyao/p/python_descriptor.html

  • 相关阅读:
    Elementary Methods in Number Theory Exercise 1.2.25
    Elementary Methods in Number Theory Exercise 1.2.14
    图解欧几里德算法
    图解欧几里德算法
    Elementary Methods in Number Theory Exercise 1.2.14
    Android中的长度单位详解(dp、sp、px、in、pt、mm)
    分享下多年积累的对JAVA程序员成长之路的总结
    android异常之都是deamon惹的祸The connection to adb is down, and a severe error has occured.
    TomatoCartv1.1.8.2部署时报错
    JavaScript浏览器对象之二Document对象
  • 原文地址:https://www.cnblogs.com/z-x-y/p/10216249.html
Copyright © 2011-2022 走看看