Python高级用法:描述符(descriptor)

描述符

描述符允许自定义在引用一个对象的属性时应该完成的事情。它是一个类,定义了另一个类的属性的访问方式。换句话说,一个类可以将属性管理委托给另一个类。

描述符类基于3个特殊方法,这3个方法组成了描述符协议(descriptor protocol):

  • __set__(self, obj, type=None):在设置属性时将调用这一方法。在下面的示例中,我们将其称为setter。
  • __get__(self, obj, value):在读取属性时将调用这一方法(被称为getter)。
  • __delete__(self, obj):对属性调用del时将调用这一方法。

实现了__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实例化对象时才会初始化,并且只会被初始化一次。

你可能感兴趣的:(python高级用法,python,开发语言)