zoukankan      html  css  js  c++  java
  • 神奇的描述符(三):覆盖描述符与非覆盖描述符

    描述符可以细分为覆盖型描述符与非覆盖型描述符。

    可以按如下规则区分它们:

    • 实现 __set__ 方法的类,称之为“覆盖型描述符”
    • 没有实现 __set__ 方法的类,但实现了__get__方法的类,称之为“非覆盖型描述符”
    • 同时实现 __set__ 和 __get__ 方法的类,通常称之为“强制描述符”。

     先分别定义三种类型的描述符,及它们托管的类,为后续的测试及验证做准备。

    class OverAll:
    
        """
        描述符是指含有__get__ 、 __set__ 、__delete__方法的类, 
        对于实现了 __set__方法的类,称之为覆盖描述符,
        如果同时也实现了 __get__ 方法,则称之为强制描述符
        """
    
        def __get__(self, instance, owner):
            print("强制描述符 __get__ 被运行")
            return self, instance, owner
    
        def __set__(self, instance, value):
            print("强制描述符 __set__ 被运行")
            return self, instance, value
    
        def __delete__(self, instance):
            print("强制描述符 __delete__ 被运行")
    
    
    class OnlySet:
        """
        对于覆盖型描述符,因为实现了__set__方法,
        会覆盖掉实例属性的赋值操作
        但不会影响类属性的赋值操作
        """
        def __set__(self, instance, value):
            print("覆盖型描述符 __set__ 被运行")
            return self, instance, value
    
    
    class OnlyGet:
        """
        如果描述符没有实现 __set__ 方法(即只有 __get__方法),
        则称之为非覆盖型描述符
        """
    
        def __get__(self, instance, owner):
            print("非覆盖型描述符 __get__ 被运行")
            return self, instance, owner
    
    
    class Spam:
        """
        定义一个类,类属性是上面三种描述符
        """
        over_all = OverAll()
        only_set = OnlySet()
        only_get = OnlyGet()
    
    # 实例化Spam
    spam = Spam()

    覆盖型描述符

    通常情况下,覆盖型描述符会同时实现__get__与__set__方法,但也可以只实现__set__方法。如果在描述符中定义了__set__方法,那么描述符会接管对这个实例属性的赋值。

    在下面这个例子中,对only_set进行赋值,会触发描述符的__set__方法,打印出“覆盖型描述符 __set__ 被运行”

    spam.only_set = 2

    如果直接操作实例属性的__dict__对其进行赋值,描述符不会生效,即使定义了__set__方法。

    spam.__dict__['only_set'] = '描述符不会执行'

    如果覆盖型描述符没有定义__get__方法,那么描述符不会接管实例属性的读取,所以从实例读取only_set时,实际上读取的是类属性only_set,所以会返回描述符对象。

    print(spam.only_set)
    # <__main__.OnlySet object at 0x000001A3C49575C0>

    需要额外注意的是,描述符的__set__方法,只能接管实例属性的赋值,无法接管类属性的赋值,所以对类属性的赋值是无能为力的。

    下面的例子可以看到,即使完整定义了__get__、__set__、__delete__方法的强制描述符, 都无法接管类属性的赋值。

    # 对类属性描述符进行赋值,会直接把描述符覆盖掉,并且不会触发描述符的__set__方法
    Spam.over_all = 1
    # 因为作为类属性的描述符已经被覆盖,所以会打印出 1
    print(Spam.over_all)

    如果需要使用描述符接管类属性的赋值操作,需要通过在元类中定义描述符来实现。


    非覆盖型描述符

    当通过实例访问非覆盖型描述符时,描述符的__get__方法会被执行,打印出“非覆盖型描述符 __get__ 被运行”的字符串。

    # 获取实例的非覆盖型描述符时,会发现描述符的 __get__ 方法被执行
    print(spam.only_get)
    # 非覆盖型描述符 __get__ 被运行
    # (<__main__.OnlyGet object at 0x000002C120637668>, <__main__.Spam object at 0x000002C1206376A0>, <class '__main__.Spam'>)

    与__set__方法不同的是,__get__方法不仅会接管实例属性的访问,还会接管类属性的访问。

    下面的例子中,通过类直接访问only_get,描述符的__get__方法被执行,打印出“非覆盖型描述符 __get__ 被运行”的字符串。

    print(Spam.only_get)
    # 非覆盖型描述符 __get__ 被运行
    # (<__main__.OnlyGet object at 0x00000283A7C27630>, None, <class '__main__.Spam'>)

    如果对实例属性赋值,因为非覆盖型描述符进行*没有*定义__set__方法,无法接管对实例属性的赋值,会在实例中创建一个同名的属性,导致描述符失效。

    # 对实例的非覆盖描述符进行赋值,因为没有 __set__ 方法
    # 所以描述符本身没有接管实例属性的赋值操作
    spam.only_get = 3
    # 这个时候,会实例属性进行取值,就会发现描述符的 __get__ 方法也不会执行
    # 所以打印结果是 3
    print(spam.only_get)

    出现这种情况的原因是通过实例去获取属性时,优先返回的是实例自身的属性,只有在实例自身没有这个属性的情况下,才会到类中查找对应的属性。而描述符本身也是属于类属性,所以受到这个规则的影响。

    spam.only_get = 3 ,对 only_get 进行赋值时,实际上已经在实例中创建出一个同名的属性 only_get, 后续再对 only_get 进行读取,获取的是实例的属性only_get,而不是作为类属性的描述符 only_get。所以 描述符的 __get__ 方法不会触发。

    《Python Cookbook》8.10 让属性具有惰性求值的能力,即是用非覆盖型描述符,让实例属性的读取实现惰性求值,以提高执行性能。核心的逻辑就是在描述符执行__get__方法时,同时对同名实例属性进行赋值,覆盖掉描述符自身。那么在下次读取时,直接读取的便是实例自身的属性,不会再调用描述符的__get__进行运算,以达到提高性能的目的。

    如果需要恢复描述符的正常功能,需要将对应的实例属性删除。下面的代码中,删除了实例属性only_get之后,描述符恢复正常工作。

    # 将实例属性删除,再获取一次 spam.only_get ,就会发现描述符的__get__正常执行了
    del spam.only_get
    print(spam.only_get)
    # (<__main__.OnlyGet object at 0x0000017EFBAC7668>, <__main__.Spam object at 0x0000017EFBAC76A0>, <class '__main__.Spam'>)
  • 相关阅读:
    java 计算文件 md5值
    前端项目结构
    路由器下连接路由器教程
    idea 中抽取部分代码
    idea 多行注释 Ctrl shift / 失效问题
    idea Ctrl+shift+F 快捷键失效原因
    Android studio 安装 jrebel for Android 下载不了问题
    利用C#创建和安装一个windows服务
    25条提高Visual Studio编码和调试效率的技巧
    IIS解决 上传文件大小限制
  • 原文地址:https://www.cnblogs.com/blackmatrix/p/7238004.html
Copyright © 2011-2022 走看看