前言

根据是否定义__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__ 方法的描述符处理。

05-13 22:46