python 面向对象专题(九):特殊方法 (二)__get__、__set__、__delete__ 描述符(二)覆盖型与非覆盖型描述符对比

前言

根据是否定义__set__ 方法,描述符可分为两大类。

实现 __set__ 方法的描述符属于覆盖型描述符,因为虽然描述符是类属性,但是实现 __set__ 方法的话,会覆盖对实例属性的赋值操作。

没有实现 __set__ 方法的描述符是非覆盖型描述符。如果设置了同名的实例属性,描述符会被遮盖,致使描述符无法处理那个实例的那个属性。

示例 20-8 descriptorkinds.py:几个简单的类,用于研究描述符的覆盖行为

### 辅助函数,仅用于显示 ###
def cls_name(obj_or_cls):
    cls = type(obj_or_cls)
    if cls is type:
        cls = obj_or_cls
    return cls.__name__.split('.')[-1]

def display(obj):
    cls = type(obj)
    if cls is type:
        return '<class {}>'.format(obj.__name__)
    elif cls in [type(None), int]:
        return repr(obj)
    else:
        return '<{} object>'.format(cls_name(obj))
def print_args(name, *args):
    pseudo_args = ', '.join(display(x) for x in args)
    print('-> {}.__{}__({})'.format(cls_name(args[0]), name, pseudo_args))

### 对这个示例重要的类 ###
class Overriding:  ➊
    """也称数据描述符或强制描述符"""
    def __get__(self, instance, owner):
        print_args('get', self, instance, owner)  ➋
    def __set__(self, instance, value):
        print_args('set', self, instance, value)

class OverridingNoGet:  ➌
    """没有``__get__``方法的覆盖型描述符"""
    def __set__(self, instance, value):
        print_args('set', self, instance, value)

class NonOverriding:  ➍
    """也称非数据描述符或遮盖型描述符"""
    def __get__(self, instance, owner):
        print_args('get', self, instance, owner)

class Managed:  ➎
    over = Overriding()
    over_no_get = OverridingNoGet()
    non_over = NonOverriding()
    def spam(self):  ➏
        print('-> Managed.spam({})'.format(display(self)))

❶ 有 __get__ 和 __set__ 方法的典型覆盖型描述符。

❷ 在这个示例中,各个描述符的每个方法都调用了 print_args 函数。

❸ 没有 __get__ 方法的覆盖型描述符。
❹ 没有 __set__ 方法,所以这是非覆盖型描述符。
❺ 托管类,使用各个描述符类的一个实例。
❻ spam 方法放在这里是为了对比,因为方法也是描述符。
在接下来的几节中,我们要分析对 Managed 类及其实例做属性读写时
的行为,还会讨论所定义的各个描述符。

2 覆盖型描述符

2.1 覆盖型描述符

实现 __set__ 方法的描述符属于覆盖型描述符,因为虽然描述符是类属性,但是实现 __set__ 方法的话,会覆盖对实例属性的赋值操作。

示例 20-9 覆盖型描述符的行为,其中 obj.over 是 Overriding类(见示例 20-8)的实例

    >>> obj = Managed()  ➊
    >>> obj.over  ➋
    -> Overriding.__get__(<Overriding object>, <Managed object>,
        <class Managed>)
    >>> Managed.over  ➌
    -> Overriding.__get__(<Overriding object>, None, <class Managed>)
    >>> obj.over = 7-> Overriding.__set__(<Overriding object>, <Managed object>, 7)
    >>> obj.over  ➎
    -> Overriding.__get__(<Overriding object>, <Managed object>,
        <class Managed>)
    >>> obj.__dict__['over'] = 8>>> vars(obj)  ➐
    {'over': 8}
    >>> obj.over  ➑
    -> Overriding.__get__(<Overriding object>, <Managed object>,
        <class Managed>)

❶ 创建供测试使用的 Managed 对象。
❷ obj.over 触发描述符的 __get__ 方法,第二个参数的值是托管实例 obj。
❸ Managed.over 触发描述符的 __get__ 方法,第二个参数(instance)的值是 None。
❹ 为 obj.over 赋值,触发描述符的 __set__ 方法,最后一个参数的值是 7。
❺ 读取 obj.over,仍会触发描述符的 __get__ 方法。
❻ 跳过描述符,直接通过 obj.__dict__ 属性设值。
❼ 确认值在 obj.__dict__ 属性中,在 over 键名下。
❽ 然而,即使是名为 over 的实例属性,Managed.over 描述符仍会覆盖读取 obj.over 这个操作。

2.2 没有 __get__ 方法的覆盖型描述符

示例 20-10 没有 __get__ 方法的覆盖型描述符,其中obj.over_no_get 是 OverridingNoGet 类(见示例 20-8)的实例

    >>> obj.over_no_get  ➊
    <__main__.OverridingNoGet object at 0x665bcc>
    >>> Managed.over_no_get  ➋
    <__main__.OverridingNoGet object at 0x665bcc>
    >>> obj.over_no_get = 7-> OverridingNoGet.__set__(<OverridingNoGet object>, <Managed object>, 7)
    >>> obj.over_no_get  ➍
    <__main__.OverridingNoGet object at 0x665bcc>
    >>> obj.__dict__['over_no_get'] = 9>>> obj.over_no_get  ➏
    9
    >>> obj.over_no_get = 7-> OverridingNoGet.__set__(<OverridingNoGet object>, <Managed object>, 7)
    >>> obj.over_no_get  ➑
    9

❶ 这个覆盖型描述符没有 __get__ 方法,因此,obj.over_no_get 从类中获取描述符实例。
❷ 直接从托管类中读取描述符实例也是如此。
❸ 为 obj.over_no_get 赋值会触发描述符的 __set__ 方法。
❹ 因为 __set__ 方法没有修改属性,所以在此读取 obj.over_no_get获取的仍是托管类中的描述符实例。
❺ 通过实例的 __dict__ 属性设置名为 over_no_get 的实例属性。
❻ 现在,over_no_get 实例属性会遮盖描述符,但是只有读操作是如此。
❼ 为 obj.over_no_get 赋值,仍然经过描述符的 __set__ 方法处理。
❽ 但是读取时,只要有同名的实例属性,描述符就会被遮盖

3 非覆盖型描述符

示例 20-11 非覆盖型描述符的行为,其中 obj.non_over 是NonOverriding 类(见示例 20-8)的实例

    >>> obj = Managed()
    >>> obj.non_over  ➊
    -> NonOverriding.__get__(<NonOverriding object>, <Managed object>,
        <class Managed>)
    >>> obj.non_over = 7>>> obj.non_over  ➌
    7
    >>> Managed.non_over  ➍
    -> NonOverriding.__get__(<NonOverriding object>, None, <class Managed>)
    >>> del obj.non_over  ➎
    >>> obj.non_over  ➏
    -> NonOverriding.__get__(<NonOverriding object>, <Managed object>,
        <class Managed>)

❶ obj.non_over 触发描述符的 __get__ 方法,第二个参数的值是obj。
❷ Managed.non_over 是非覆盖型描述符,因此没有干涉赋值操作的__set__ 方法。
❸ 现在,obj 有个名为 non_over 的实例属性,把 Managed 类的同名描述符属性遮盖掉。
❹ Managed.non_over 描述符依然存在,会通过类截获这次访问。
❺ 如果把 non_over 实例属性删除了……
❻ 那么,读取 obj.non_over 时,会触发类中描述符的 __get__ 方法;但要注意,第二个参数的值是托管实例。

在上述几个示例中,我们为几个与描述符同名的实例属性赋了值,结果依描述符中是否有 __set__ 方法而有所不同。

依附在类上的描述符无法控制为类属性赋值的操作。其实,这意味着为类属性赋值能覆盖描述符属性

4 在类中覆盖描述符

不管描述符是不是覆盖型,为类属性赋值都能覆盖描述符。这是一种猴子补丁技术,不过在示例 20-12 中,我们把描述符替换成了整数,这其实会导致依赖描述符的类不能正确地执行操作。

示例 20-12 通过类可以覆盖任何描述符

    >>> obj = Managed()  ➊
    >>> Managed.over = 1>>> Managed.over_no_get = 2
    >>> Managed.non_over = 3
    >>> obj.over, obj.over_no_get, obj.non_over  ➌
    (1, 2, 3)

❶ 为后面的测试新建一个实例。
❷ 覆盖类中的描述符属性。
❸ 描述符真的不见了。


示例 20-12 揭示了读写属性的另一种不对等:读类属性的操作可以由依附在托管类上定义有 __get__ 方法的描述符处理,但是写类属性的操作不会由依附在托管类上定义有 __set__ 方法的描述符处理。



原文地址:https://www.cnblogs.com/qiu-hua/p/12916380.html