• Python 描述符是什么?以及如何实现


    先看一个例子,@property。被@property修饰的成员函数,将变为一个描述符。这是最简单的创建描述符的方式。

    class Foo:
      @property
      def attr(self):
        print('getting attr')
        return 'attr value'
    
      def bar(self): pass
    
    foo = Foo()
    

    上面这个例子中, attr 是类 Foo 的一个成员函数,可通过语句 foo.attr() 被调用。 但当它被 @property 修饰后,这个成员函数将不再是一个函数,而变为一个描述符。 bar 是一个未被修饰的成员函数。 type(Foo.attr)type(Foo.bar) 的结果分别为:

    <type 'property'>
    <type 'instancemethod'>
    

    attr 的类型为 property (注:一个 property 类型的对象总是一个描述符), bar 的类型为 instancemethod ,也即一个常规的成员函数。

    此时 attr 将无法再被调用,当尝试调用它时,语句 foo.attr() 将抛出错误:

    TypeError: 'str' object is not callable
    

    让我们来理解这个错误。

    首先来看 foo.attr 的值:

    attr value
    

    其类型 type(foo.attr)

    str
    

    foo.attr 的类型为 str ,因此便有了以上的错误,一个 str 对象无法被调用。其值为'attr value',正好是原始 attr 函数的返回值。 因此语句 foo.attr 实际上触发了原始 attr 函数的调用,并且将函数的返回值作为其值。实际上语句 print(foo.attr) 的输出为:

    getting attr
    attr value
    

    进一步验证了执行语句 foo.attr 时,原始的 attr 函数被调用。

    发生了什么?当执行一个访问对象属性的语句 foo.attr 时,结果一个函数调用被触发!这便是描述符的作用:将属性访问转变为函数调用,并由这个函数来控制这个属性的值(也即函数的返回值),以及在返回值前做定制化的操作。此时可以给描述符一个简要定义:

    描述符是类的一个属性,控制类实例对象访问这个属性时如何返回值及做哪些额外操作

    这留给程序员的空间是巨大的。。

    描述符协议

    任何实现了描述符协议的类都可以作为描述符类。描述符协议为一组成员函数定义,包括:

    函数作用返回值是否必须
    __get__(self, obj, type) 获取属性值 属性的值
    __set__(self, obj, value) 设置属性的值 None
    __delete__(self, obj) 删除属性 None

    如果一个类实现了以上成员函数,则它便是一个描述符类,其实例对象便是一个描述符

    下面是一个自定义的描述符的实现。

    class MyDescriptor:
      def __init__(self):
        self.data = None
      def __get__(self, obj, type):
        print('get called')
        return self.data
      def __set__(self, obj, value):
        print('set called')
        self.data = value
      def __delete__(self, obj):
        print('delete called')
        del self.data
    
    class Foo:
      attr = MyDescriptor()
    
    foo = Foo()
    

    示例中 MyDescriptor 实现了描述符协议(也即实现了 __get__, __set__, __delete__ 函数),因此其为一个描述符类。 Fooattr 属性为 MyDescriptor 类的实例对象,因此它是一个描述符。

    print(foo.attr) 的输出为:

    get called
    None
    

    可见当访问 fooattr 属性时, MyDescriptor__get__ 函数被调用。

    foo.attr = 'new value' 的输出为:

    set called
    

    可见当为 attr 设置一个新值时, MyDescriptor__set__ 函数被调用。

    再运行 print(foo.attr) ,输出为:

    get called
    new value
    

    可见新值已被设置。

    del foo.attr 的输出为:

    delete called
    

    可见当为删除属性 attr 时, MyDescriptor__delete__ 函数被调用。

    再执行 print(foo.attr)AttributeError 被抛出:

    get called
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "1.py", line 6, in __get__
        return self.data
    AttributeError: 'MyDescriptor' object has no attribute 'data'
    

    可见属性 attr 已被删除。

    参数意义

    __get__(self, obj, type) 函数各个参数的意义为:

    参数意义例子中的对应
    self 描述符对象本身 Foo.attr
    obj 使用描述符的对象实例 foo
    type obj的类型 Foo

    __set__(self, obj, value) 函数的self和obj参数的意义同 __get__ ,value的意义为:

    参数意义例子中的对应
    value 属性的新值 'new value'

    __delete__(self, obj) 函数的self和obj参数的意义同 __get__

    (全文完)

  • 相关阅读:
    Java 中的 匿名类
    spring boot 中容器 Jetty、Tomcat、Undertow
    微信小程序-动态设置背景色navigationBarBackgroundColor的值
    vue el-tree:默认展开第几级节点
    safarai
    微信小程序-输入框输入文字后,将光标移到文字中间,接着输入文字后光标又自动跳到最后
    微信小程序-WebSocket应用
    微信小程序-聊天列表-角标
    SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data错误的解决
    button 去掉原生边框
  • 原文地址:https://www.cnblogs.com/astropeak/p/9032271.html
Copyright © 2020-2023  润新知