一、什么是描述符?
简单的说,首先要有一个实现了__get__()、__set__()、__delete__()中任意一种方法的新式类(Python 2.x版本默认旧式类,通过继承object为新式类),并且这个新式类的实例对象是另外一个类的属性,这个属性就被称之为描述符。
class MyDescriptor:
def __get__(self, instance, owner):
print('get called')
return 'get'
def __set__(self, instance, value):
print('set called')
def __delete__(self, instance):
print('delete called')
class Foo:
attr = MyDescriptor() # 描述符
上面代码中Foo的属性attr是类MyDescriptor的实例化对象,MyDescriptor实现了__get__()
、__set__()
和__delete__()
,那么attr就是成为了描述符,注意attr必须用类属性形式写。
二、描述符有什么用?
1.类型检查
由于Python
是一个动态类型解释性语言,不像C/C++
等静态编译型语言,数据类型在编译时便可以进行验证,而Python
中必须添加额外的类型检查逻辑代码才能做到这一点,这就是描述符的初衷。比如,有一个测试类Test
,其具有一个类属性name。
class Test(object):
name = None
正常情况下,name
的值(其实应该是对象, name
是引用)都应该是字符串,但是因为Python
是动态类型语言,即使执行Test.name = 3
,解释器也不会有任何异常。当然可以想到解决办法,就是提供一个getter
,setter
方法来统一读写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")
虽然以上代码勉强可以实现对属性赋值的类型检查,但是会导致类型定义的臃肿和逻辑的混乱,而描述符就恰好能解决这一问题。
为name
属性定义一个(数据)描述符类,其实现了__get__
和__set__
方法,代码如下:
class NameDes(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")
当实例对象访问name属性时,就会调用__get__方法,打印name属性所需要的值,当使用实例对象对name属性进行修改或赋值时,则会调用__set__方法,这时只需要在set方法添加所需限制条件,就可以限制name属性的类型,弥补了python动态类型的缺陷。
2.弥补property属性的缺陷
property属性和描述符有一个相似点,那就是都可以对属性进行限制操作(不了解的建议去补下property属性)
代码如下:
class Student(object):
def __init__(self, hight):
self._hight = hight # 单位cm
@property
def hight(self):
return self._hight
@hight.setter
def hight(self, value):
# 判断输入的类型
if not isinstance(value, int):
raise TypeError
# 判断是否符合逻辑
if value >= 300 or value < 0:
raise ValueError
# 进行修改
self._hight = value
@hight.deleter
def hight(self):
del self._hight
s01 = Student(175)
print(s01.hight) # 175
s01.hight = 255
print(s01.hight) # 255
s01.hight = "hello" # TypeError 类型错误
del s01.hight
print(s01.hight)
通过上述代码可以看出property属性也可以限制属性的修改赋值,但也发现,s01.hight看似调用的是hight这一实例属性,却是隐藏调用的getter/setter方法,看起来很臃肿。
对property
来说,最大的缺点就是它们不能重复使用。
如果只是对一个属性进行限制的话,那么property属性用起来也没有多大问题,但如果同时需要限制多个属性时,就需要多组getter/setter/deleter方法来实现,极大的降低了代码的复用性。
这就是描述符所解决的问题。描述符是property
的升级版,允许你为重复的property
逻辑编写单独的类来处理。