boxmoe_header_banner_img

Hello! 欢迎来到悠悠畅享网!

文章导读

Python中描述符protocol 描述符协议__get__和__set__实现原理


avatar
悠悠站长 2025年6月6日 5

描述符是python中用于自定义属性访问行为的对象,其核心在于实现__get__、__set__和__delete__方法。1. __get__用于获取属性值,当访问属性时触发;2. __set__用于设置属性值,控制赋值过程;3. __delete__用于删除属性。描述符必须作为类属性存在才生效,且数据描述符(含__set__或__delete__)优先级高于实例字典。若将值存储在描述符自身,会导致所有实例共享该值,应避免此做法。掌握描述符机制有助于深入理解属性访问、装饰器及property等内置工具的实现原理,并可用于高级特性如类型检查和自动属性管理。

在Python中,描述符(Descriptor)是一种实现特定协议的对象,这个协议包含 __get__、__set__ 和 __delete__ 方法。通过这些方法,我们可以自定义类属性的访问行为。理解描述符的工作机制,有助于更深入掌握属性访问、装饰器、以及像 property 这样的内置工具背后的原理。

描述符协议的基本结构

描述符的核心在于它定义了如何通过类或实例访问属性。一个对象只要实现了以下任意一个方法,就可以被称为描述符:

  • __get__(self, instance, owner):用于获取属性值。
  • __set__(self, instance, value):用于设置属性值。
  • __delete__(self, instance):用于删除属性。

这些方法中的参数含义如下:

  • self:描述符实例本身。
  • instance:使用该描述符的类实例(如果是通过实例访问的话)。
  • owner:使用该描述符的类(即实例所属的类)。

比如,当你访问 obj.attr 时,如果 attr 是一个描述符对象,那么实际上会调用它的 __get__ 方法。

立即学习Python免费学习笔记(深入)”;

__get__ 的工作方式

当访问某个属性时,Python解释器会在类和实例的命名空间中查找该属性。如果找到的是一个实现了 __get__ 的对象,并且这个对象是“数据描述符”(有 __set__ 或 __delete__)或者“非数据描述符”,那么就会触发 __get__ 方法。

举个例子:

class MyDescriptor:     def __get__(self, instance, owner):         print("Getting value")         return 42  class MyClass:     attr = MyDescriptor()  obj = MyClass() print(obj.attr)

输出为:

Getting value 42

在这个例子中,访问 obj.attr 实际上调用了 MyDescriptor.__get__ 方法。这里的关键点是,描述符必须作为类属性存在于另一个类中才会生效,不能直接赋值给实例属性。

__set__ 的作用与限制

如果你希望描述符可以控制属性的赋值过程,就需要实现 __set__ 方法。这个方法会在你执行类似 obj.attr = value 的操作时被调用。

来看一个简单的实现:

class MyDescriptor:     def __get__(self, instance, owner):         print("Getting value")         return self.value      def __set__(self, instance, value):         print("Setting value")         self.value = value  class MyClass:     attr = MyDescriptor()  obj = MyClass() obj.attr = 100 print(obj.attr)

输出:

Setting value Getting value 100

注意几点:

  • 如果一个描述符没有实现 __set__,那它就是一个“非数据描述符”,这时候实例字典中可以直接覆盖这个属性。
  • 如果描述符同时实现了 __get__ 和 __set__,它就是“数据描述符”,这时实例字典中的同名属性会被忽略。
  • 描述符的 __set__ 方法通常会把值保存在实例上而不是描述符自身,否则多个实例共享同一个值。

例如,在上面的例子中,如果我们想让每个 MyClass 实例都有自己的值,应该把值存在 instance 上:

def __set__(self, instance, value):     instance._value = value  def __get__(self, instance, owner):     return instance._value

这样就能避免不同实例之间的值互相干扰。

常见误区与注意事项

使用描述符时有几个常见的坑需要注意:

  • 不要把值存在描述符自己身上:这会导致所有使用该描述符的实例共享同一个值。
  • 确保描述符是类属性:如果不是放在类中而是赋值给实例,描述符不会起作用。
  • 区分数据描述符和非数据描述符:前者优先级高于实例字典,后者则相反。
  • 避免循环引用:在 __get__ 或 __set__ 中不小心修改了自身属性,可能会导致无限递归

基本上就这些内容了。描述符是Python底层机制的一部分,虽然不常用,但在实现高级特性如类型检查、自动属性管理等方面非常有用。掌握其原理,能让你写出更灵活、可控的类设计。



评论(已关闭)

评论已关闭