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

猜你喜欢

转载自www.cnblogs.com/z-x-y/p/10216249.html