• 描述符详解


    初学py的时候大家都说描述符是高级内容难度较大,仔细撸过文档之后感觉还好,不过用起来确实不那么直观。

    按照惯例,先来看一下官文API文档:

    In general, a descriptor is an object attribute with “binding behavior”, one whose attribute access has been overridden by methods in the descriptor protocol: __get__(), __set__(), and __delete__().
    If any of those methods are defined for an object, it is said to be a descriptor.
    

    总的来说,描述符是一个带有绑定行为的对象属性,访问这个对象属性的时候会被描述符协议中的方法覆盖,可以理解为一种hook机制。

    The following methods only apply when an instance of the class containing the method (a so-called descriptor class) appears in an owner class.
    the descriptor must be in either the owner’s class dictionary or in the class dictionary for one of its parents.
    

    描述符协议包括三个魔术方法:

    • object.get(self, instance, owner):通过owner class或其实例访问描述符实例时被调用。
    • object.set(self, instance, value):给owner class的中的描述符实例赋值的时候被调用。
    • object.delete(self, instance):删除owner class中的描述符实例的时候被调用,这个方法很少用。

    一个对象只要实现其中之一就是一个描述符。
    描述符必须在owner class或者其父类的属性字典中,也就是owner的类属性,定义成实例属性无效。

    For instance bindings, the precedence of descriptor invocation depends on the which descriptor methods are defined.
    A descriptor can define any combination of __get__(), __set__() and __delete__().
    If it does not define __get__(), then accessing the attribute will return the descriptor object itself unless there is a value in the object’s instance dictionary.
    If the descriptor defines __set__() and/or __delete__(), it is a data descriptor. 
    if it defines neither, it is a non-data descriptor. 
    
    Normally, data descriptors define both __get__() and __set__(), while non-data descriptors have just the __get__() method.
    Data descriptors with __set__() and __get__() defined always override a redefinition in an instance dictionary.
    In contrast, non-data descriptors can be overridden by instances.
    

    如果描述符class中没有定义__get__()方法,那么访问描述符实例仅仅会返回一个描述符class的实例,而不会触发调用。
    如果定义了__set__()和__delete__ ()其中之一就是一个数据描述符
    如果都没有定义,即只定义__get__,那么就是一个非数据描述符

    通常,数据描述符会同时定义__get__()和__set__(),非数据描述符仅定义__get__()。

    其实,基础概念看到这里就可以了,弄清什么时候触发__get__()、什么时候触发__set__()即可。
    至于描述符实例啥时候会被覆盖这一点经常会把萌新弄晕,而且这一点属于面向对象基础知识,跟描述符本身无关。


    下面举两个例子简单看一下非数据描述符和数据描述符。

    非数据描述符:

    class A:
        def __init__(self):
            self.a = 'a'
            print('A init')
    
        def __get__(self, instance, owner):
            print('A.__get__ {} {} {}'.format(self, instance, owner))
            return self
    
    class B:
        x = A()
        def __init__(self):
            self.b = 'b'  # 这个仅仅是一个实例属性,与描述符无关
            # self.x = 'x'   # 由于描述符没有定义__set__方法,这里不能这样定义,实例化的时候实例属性会覆盖类属性,描述符将被覆盖
            print('B init')
    
    print(B.x)  # 这里会调用A的__get__方法,返回的是__get__方法的返回值
    # A init
    # A.__get__ <__main__.A object at 0x10302d5c0> None <class '__main__.B'>
    # <__main__.A object at 0x10302d5c0>
    
    b = B()
    print(b.x)  # 这里也会调用A的__get__方法,不过这里会把b实例传递进去
    # B init
    # A.__get__ <__main__.A object at 0x10302d5c0> <__main__.B object at 0x10302d748> <class '__main__.B'>
    # <__main__.A object at 0x10302d5c0>
    
    # 为了能够获取描述符实例的属性,可以让__get__方法返回self
    print(b.x.a)
    # A.__get__ <__main__.A object at 0x10302d5c0> <__main__.B object at 0x10302d748> <class '__main__.B'>
    # a
    
    print(b.b)
    # b
    

    数据描述符:

    class A:
        def __init__(self):
            self.a = 'a'
            print('A init')
    
        def __get__(self, instance, owner):
            print('A.__get__ {} {} {}'.format(self, instance, owner))
            return self
    
        def __set__(self, instance, value):
            print('A.__set__ {} {} {}'.format(self, instance, value))
            instance.c = value
    
    class B:
        x = A()
        def __init__(self):
            self.b = 'b'
            self.x = 'c'    # 由于描述符定义了__set__方法,这里对描述符实例的赋值操作会调用描述符的__set__方法,并没有覆盖描述符
            print('B init')
    
    b = B()
    print(b.__dict__)
    # {'b': 'b', 'c': 'c'}
    
    b.x = 100
    print(b.__dict__)
    # {'b': 'b', 'c': 100}
    

    由于涉及两个类和其实例的交互,阐述起来也不是很容易呢。不过仔细捋一下,其实并不是很复杂,有没有?


    最后,需要提醒一下,staticmethod、classmethod、property三个内置装饰器都是通过描述符实现的。
    其中,staticmethod和classmethod是通过非数据描述符实现的,property是通过数据描述符实现的。

    Python methods (including staticmethod() and classmethod()) are implemented as non-data descriptors.
    The property() function is implemented as a data descriptor.
    

    通常大家知道怎么用这几个装饰器,但往往可能不会细究是如何实现的,下篇继续来说这几个装饰器是如何实现的。

    参考:
    https://docs.python.org/3/reference/datamodel.html#implementing-descriptors
    https://docs.python.org/3/reference/datamodel.html#invoking-descriptors

  • 相关阅读:
    Fiddler配置及使用教程
    Fiddler模拟限速实战
    Fiddler之模拟响应、修改请求或响应数据(断点)
    Fiddler修改请求数据
    Fiddler基础用法-抓取浏览器数据包
    Fiddler高级用法-抓取手机app数据包
    计算机网络基础:可靠传输原理
    计算机网络基础:TCP和UDP
    计算机网络基础:帧结构 + 以太网
    计算机网络基础:TCP/IP协议栈
  • 原文地址:https://www.cnblogs.com/keithtt/p/10223247.html
Copyright © 2020-2023  润新知