Python中的descriptor描述器简明使用指南

558次阅读  |  发布于5年以前

当定义迭代器的时候,描述是实现迭代协议的对象,即实现iter方法的对象。同理,所谓描述器,即实现了描述符协议,即get, set, 和 delete方法的对象。

单看定义,还是比较抽象的。talk is cheap。看代码吧:


    class WebFramework(object):
      def __init__(self, name='Flask'):
        self.name = name

      def __get__(self, instance, owner):
        return self.name

      def __set__(self, instance, value):
        self.name = value


    class PythonSite(object):

      webframework = WebFramework()

    In [1]: PythonSite.webframework
    Out[1]: 'Flask'

    In [2]: PythonSite.webframework = 'Tornado'

    In [3]: PythonSite.webframework
    Out[3]: 'Tornado'

定义了一个类WebFramework,它实现了描述符协议getset,该对象(类也是对象,一切都是对象)即成为了一个描述器。同时实现getset的称之为资料描述器(data descriptor)。仅仅实现get的则为非描述器。两者的差别是相对于实例的字典的优先级。

如果实例字典中有与描述器同名的属性,如果描述器是资料描述器,优先使用资料描述器,如果是非资料描述器,优先使用字典中的属性。

描述器的调用
对于这类魔法,其调用方法往往不是直接使用的。例如装饰器需要用 @ 符号调用。迭代器通常在迭代过程,或者使用 next 方法调用。描述器则比较简单,对象属性的时候会调用。


    In [15]: webframework = WebFramework()

    In [16]: webframework.__get__(webframework, WebFramework)
    Out[16]: 'Flask'

描述器的应用
描述器的作用主要在方法和属性的定义上。既然我们可以重新描述类的属性,那么这个魔法就可以改变类的一些行为。最简单的应用则是可以配合装饰器,写一个类属性的缓存。Flask的作者写了一个werkzeug网络工具库,里面就使用描述器的特性,实现了一个缓存器。


    class _Missing(object):
      def __repr__(self):
        return 'no value'

      def __reduce__(self):
        return '_missing'


    _missing = _Missing()


    class cached_property(object):
      def __init__(self, func, name=None, doc=None):
        self.__name__ = name or func.__name__
        self.__module__ = func.__module__
        self.__doc__ = doc or func.__doc__
        self.func = func

      def __get__(self, obj, type=None):
        if obj is None:
          return self
        value = obj.__dict__.get(self.__name__, _missing)
        if value is _missing:
          value = self.func(obj)
          obj.__dict__[self.__name__] = value
        return value


    class Foo(object):
      @cached_property
      def foo(self):
        print 'first calculate'
        result = 'this is result'
        return result


    f = Foo()

    print f.foo  # first calculate this is result
    print f.foo  # this is result

运行结果可见,first calculate只在第一次调用时候被计算之后就把结果缓存起来了。这样的好处是在网络编程中,对HTTP协议的解析,通常会把HTTP的header解析成python的一个字典,而在视图函数的时候,可能不知一次的访问这个header,因此把这个header使用描述器缓存起来,可以减少多余的解析。

描述器在python的应用十分广泛,通常是配合装饰器一起使用。强大的魔法来自强大的责任。描述器还可以用来实现ORM中对sql语句的"预编译"。恰当的使用描述器,可以让自己的Python代码更优雅。

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8