返回介绍

20.2 覆盖型与非覆盖型描述符对比

发布于 2024-02-05 21:59:47 字数 7692 浏览 0 评论 0 收藏 0

如前所述,Python 存取属性的方式特别不对等。通过实例读取属性时,通常返回的是实例中定义的属性;但是,如果实例中没有指定的属性,那么会获取类属性。而为实例中的属性赋值时,通常会在实例中创建属性,根本不影响类。

这种不对等的处理方式对描述符也有影响。其实,根据是否定义 __set__ 方法,描述符可分为两大类。若想观察这两类描述符的行为差异,则需要使用几个类。我们将使用示例 20-8 中的代码作为接下来几节的试验台。

 在示例 20-8 中,每个 __get__ 和 __set__ 方法都调用了 print_args 函数,使调用方式易于阅读。没必要深入理解 print_args 函数及辅助函数 cls_name 和 display,因此不要花心思研究它们。

示例 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 类及其实例做属性读写时的行为,还会讨论所定义的各个描述符。

20.2.1 覆盖型描述符

实现 __set__ 方法的描述符属于覆盖型描述符,因为虽然描述符是类属性,但是实现 __set__ 方法的话,会覆盖对实例属性的赋值操作。示例 20-2 就是这样实现的。特性也是覆盖型描述符:如果没提供设值函数,property 类中的 __set__ 方法会抛出 AttributeError 异常,指明那个属性是只读的。我们可以使用示例 20-8 中的代码测试覆盖型描述符的行为,如示例 20-9 所示。

示例 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 这个操作。

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

通常,覆盖型描述符既会实现 __set__ 方法,也会实现 __get__ 方法,不过也可以只实现 __set__ 方法,如示例 20-1 所示。此时,只有写操作由描述符处理。通过实例读取描述符会返回描述符对象本身,因为没有处理读操作的 __get__ 方法。如果直接通过实例的 __dict__ 属性创建同名实例属性,以后再设置那个属性时,仍会由 __set__ 方法插手接管,但是读取那个属性的话,就会直接从实例中返回新赋予的值,而不会返回描述符对象。也就是说,实例属性会遮盖描述符,不过只有读操作是如此。参见示例 20-10。

示例 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__ 方法处理。

❽ 但是读取时,只要有同名的实例属性,描述符就会被遮盖。

20.2.3 非覆盖型描述符

没有实现 __set__ 方法的描述符是非覆盖型描述符。如果设置了同名的实例属性,描述符会被遮盖,致使描述符无法处理那个实例的那个属性。方法是以非覆盖型描述符实现的。示例 20-11 展示了对一个非覆盖型描述符的操作。

示例 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__ 方法;但要注意,第二个参数的值是托管实例。

 Python 贡献者和作者讨论这些概念时会使用不同的术语。覆盖型描述符也叫数据描述符或强制描述符。非覆盖型描述符也叫非数据描述符或遮盖型描述符。

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

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

20.2.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__ 方法的描述符处理。

 若想控制设置类属性的操作,要把描述符依附在类的类上,即依附在元类上。默认情况下,对用户定义的类来说,其元类是 type,而我们不能为 type 添加属性。不过在第 21 章,我们会自己创建元类。

下面我们调转话题,分析 Python 是如何使用描述符实现方法的。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文