描述符允许自定义在引用一个对象的属性时应该完成的事情。它是一个类,定义了另一个类的属性的访问方式。换句话说,一个类可以将属性管理委托给另一个类。
描述符类基于3个特殊方法,这3个方法组成了描述符协议(descriptor protocol):
实现了__get__()和__set__()的描述符被称为数据描述符(data descriptor)。
如果只实现了__get__(),那么就被称为非数据描述符(non-data descriptor)。
class RevealAccess(object):
"""一个数据描述符,正常设定值并返回值,同时打印出记录访问的信息。"""
def __init__(self, initval=None, name='var'):
self.val = initval
self.name = name
def __get__(self, obj, objtype):
print('Retrieving', self.name)
return self.val
def __set__(self, obj, val):
print('Updating', self.name)
self.val = val
class MyClass(object):
x = RevealAccess(10, 'var "x"')
m = MyClass()
m.x
m.x = 20
运行结果为
Retrieving var "x"
Updating var "x"
通过这个例子我们可以看出,当RevealAccess类对象被调用时,触发__get__
,当对RevealAccess类对象赋值时触发__set__
。
描述符可以将类属性的初始化延迟到被实例访问时。在属性的初始化依赖全局应用上下文,或者初始化的代价很大,但在导入类的时候不能确保一定会用到这个属性的时候非常有用。
class InitOnAccess:
def __init__(self, klass, *args, **kwargs):
self.klass = klass
self.args = args
self.kwargs = kwargs
self._initialized = None
def __get__(self, instance, owner):
if self._initialized is None:
print('initialized!')
self._initialized = self.klass(*self.args, **self.kwargs)
else:
print('cached!')
return self._initialized
class MyClass:
lazily_initialized = InitOnAccess(list, "argument")
m = MyClass()
print(m.lazily_initialized)
print(m.lazily_initialized)
运行结果如下:
initialized!
['a', 'r', 'g', 'u', 'm', 'e', 'n', 't']
cached!
['a', 'r', 'g', 'u', 'm', 'e', 'n', 't']
可以看到只有在访问到InitOnAccess实例化对象时才会初始化,并且只会被初始化一次。