如何让自定义orion_field像Django字段一样作用于实例而非类?
搞定这个实例属性共享的问题!
你遇到的问题其实不是orion_field把属性搞成类级别了,而是不小心踩了Django模型里可变默认值的经典坑!
为啥会出现这个奇怪的共享现象?
看你的KPI模型里的data字段:
data = JSONField(blank=True, default={})
Django在处理模型字段的默认值时,如果用的是字典、列表这种可变对象,这个对象会在类定义的时候就创建出来,所有新生成的实例都会共享这同一个对象。也就是说,orion_entity1和orion_entity2的data其实指向的是同一个字典!你改了其中一个,另一个自然也会跟着变。
怎么解决?
第一步:修复JSONField的默认值
把可变的{}换成可调用的函数,让每个实例初始化时都生成一个全新的空字典:
class KPI(models.Model): orion_id = models.CharField(primary_key=True, unique=True, max_length=10) # 用lambda返回新字典,或者写个单独的函数都可以 data = JSONField(blank=True, default=lambda: {}) name = orion_field("name") def set(self, attribute, value): self.data[attribute]= value def get(self, attribute): return self.data[attribute]
这样每个新实例的data都是独立的,再也不会互相干扰了。
第二步:验证效果
现在再跑你的测试代码:
orion_entity1 = KPI() orion_entity2 = KPI() orion_entity1.name = "Babar" print(orion_entity2.name) >>> KeyError: 'name' # 这才是正常的,因为第二个实例的data里根本没name这个键
如果不想看到KeyError,可以给get方法加个默认值:
def get(self, attribute): return self.data.get(attribute) # 不存在的话返回None
顺便优化下你的orion_field
当前的逻辑没问题,但可以稍微调整下让它更健壮:
def orion_field(name, field=None): def getter(self): return self.data.get(name) # 用get避免KeyError def setter(self, value): self.data[name] = value return property(getter, setter)
进阶:如果想让orion_field更像Django原生字段
要是你希望这个自定义字段能在admin后台显示、支持序列化等,可以考虑继承Django的Field类实现自定义字段,但如果只是内部业务使用,当前的property方式完全够用啦。
内容的提问来源于stack exchange,提问作者Nasgar
相关产品推荐
相关产品推荐

