编程高级Python开发者

在Python中,描述符是什么?它们是如何实现的,应用于什么,和property有什么区别,以及使用时的典型错误是什么?

用 Hintsage AI 助手通过面试

答案

描述符是一个实现了__get__、__set__和__delete__之一的方法的对象。它允许控制对类属性的访问。

描述符的标准方法:

  • __get__(self, instance, owner)
  • __set__(self, instance, value)
  • __delete__(self, instance)

描述符可以分为:

  • 经典(数据描述符)— 实现了__set__,优先于普通属性;
  • 非经典(非数据描述符)— 仅实现__get__,低于普通属性。

使用示例:

class OnlyPositive: def __init__(self): self._name = '_value' def __get__(self, instance, owner): return instance.__dict__[self._name] def __set__(self, instance, value): if value < 0: raise ValueError('Value must be >= 0') instance.__dict__[self._name] = value class Account: value = OnlyPositive() def __init__(self, value): self.value = value acc = Account(10) acc.value = -1 # ValueError!

property 仅仅是用于在类级别创建描述符的语法糖。

诱导性问题

如果将描述符作为实例的属性而不是类的属性创建,会发生什么?

许多人认为描述符会工作,但事实并非如此。

正确答案:

描述符只有在作为类属性直接定义时才能工作。如果将描述符作为实例属性定义,get/__set__方法不会被调用——将调用普通的属性访问逻辑。

由于对主题细微差别的无知而导致的实际错误示例


故事

使用property而不是完整的描述符

为了验证和其他相关属性,开发者使用了简单的property,导致逻辑的频繁重复,无法在不同的类之间重用代码。


故事

在重用存储时未遵循不可变性

描述符将数据存储在其自有类的内部属性(self.x)中,而不是实例的内部存储中,这导致属性变得 "共享",不同的类实例相互覆盖值——数据在对象之间 "泄漏"。


故事

混淆数据描述符和非数据描述符

在复杂的层次中遗漏了__set__的实现,导致普通的实例属性覆盖了描述符,破坏了整个验证机制——错误并不总是出现,调试困难。