You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python中通过类名设置描述符属性时__set__方法未调用的问题

为什么给类的描述符属性赋值时,__set__没被触发?

这事儿我刚学描述符的时候也踩过坑!咱们来掰扯清楚Python这里的逻辑:

首先得明确描述符的运行规则,尤其是实例操作和类操作的区别:

  • 当你用实例(比如jane.name)访问或赋值时:
    • 如果类里对应的属性是个数据描述符(像你写的D,同时有__get__和__set__),那访问会走__get__,赋值会走__set__——这就是你看到jane.name和jane.name='Janny'正常触发方法的原因。
  • 但当你直接操作类属性(比如Person.name)时:
    • 访问的时候,Python发现这是个描述符,会调用它的__get__(所以Person.name会打印get called)。
    • 但赋值的时候就不一样了!Python会直接把类的__dict__里的name键,从原来的D()实例替换成你新赋的值(也就是'Jack')——这一步完全跳过了描述符的逻辑,自然不会触发__set__。

你可以自己验证一下,在执行Person.name='Jack'之后,运行下面的代码:

print(Person.__dict__['name'])

输出会是'Jack',而不是原来的D()对象,说明描述符已经被彻底覆盖了。

如果你想要类层面的赋值也触发__set__,那得用到元类——因为类本身是元类的实例,要拦截类的属性赋值,得在元类里重写__setattr__方法。举个例子:

class Meta(type):
    def __setattr__(cls, name, value):
        # 检查当前类属性是否是个带__set__的描述符
        if name in cls.__dict__ and hasattr(cls.__dict__[name], '__set__'):
            # 调用描述符的__set__,实例参数传None(因为是类层面操作)
            cls.__dict__[name].__set__(None, value)
        else:
            # 不是描述符的话,按正常逻辑赋值
            super().__setattr__(name, value)

# 给Person指定这个元类
class Person(metaclass=Meta):
    name = D()

这时候再执行Person.name='Jack',就会触发D的__set__方法了。

最后再总结下关键点:

  • 实例操作描述符属性:走描述符的__get__/__set__
  • 直接给类赋值描述符属性:是替换类属性,不会触发__set__
  • 要拦截类层面的赋值,必须用元类

内容的提问来源于stack exchange,提问作者thatisvivek

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:28:30