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__。
- 访问的时候,Python发现这是个描述符,会调用它的
你可以自己验证一下,在执行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
相关产品推荐
相关产品推荐

