Django模型中类属性与实例属性的差异及测试疑惑解析
Django模型中类属性与实例属性的区别详解
嘿,我来帮你彻底理清这两者的区别,结合你给出的代码和测试结果来拆解!
先搞懂Python基础:类属性 vs 实例属性
在Python里,这两者的核心差异就是是否共享:
- 类属性:属于类本身,是所有该类实例共享的“全局变量”。它存储在类的命名空间中,所有实例访问这个属性时,默认都会读取类里的那个值——除非某个实例自己创建了同名的实例属性,才会覆盖这个查找路径。
- 实例属性:属于单个实例,每个实例都有独立的副本,修改某个实例的属性完全不会影响其他实例,它存储在实例自己的
__dict__字典里。
结合你的Django模型场景分析
Django模型的实例是和数据库记录对应的,这会带来一些特殊的表现,我们结合你的代码来看:
你的测试代码为什么看起来“没区别”?
先看你写的MyClass:
class MyClass(models.Model): name = models.CharField(max_length=255) _class_attr = '' # 类属性 def __init__(self, *args, **kwargs): super(MyClass, self).__init__(*args, **kwargs) self._instance_attr = '' # 实例属性 def save(self, *args, **kwargs): # 保存前更新两个属性 self._class_attr = 'Class attribute' self._instance_attr = 'Instance attribute' super(MyClass, self).save(*args, **kwargs)
你在save方法里写的self._class_attr = 'Class attribute',并不是修改类属性,而是给当前MyClass实例创建了一个同名的实例属性!这时候这个实例会优先用自己的_class_attr,而不再读取类里的那个。
所以你的测试结果就很好解释了:
- 从数据库
get出来的first和second实例,一开始没有自己的_class_attr实例属性,所以读取的是类属性的空字符串;_instance_attr是在__init__里初始化的实例属性,所以也是空。 - 当你调用
first.save()后,first实例多了一个自己的_class_attr实例属性,值为'Class attribute',_instance_attr也被更新。但second实例没有做过这个赋值,所以还是读取类的_class_attr空值。 - 如果你重新
get(first),会得到一个新的实例对象,这个新实例没有之前创建的_class_attr实例属性,所以又会读取类的空值——因为这些非模型字段的属性不会被Django持久化到数据库里。
真正体现类属性共享性的测试
如果你直接修改类的属性,而不是实例的,就能看到差异了:
>>> from myapp.models import MyClass >>> MyClass._class_attr = '全局更新的类属性' >>> first = MyClass.objects.get(pk=1) >>> first._class_attr # 读取类属性的值 '全局更新的类属性' >>> second = MyClass.objects.get(pk=2) >>> second._class_attr # 所有实例都共享这个值 '全局更新的类属性'
回到你的Bar模型两种写法的区别
写法1:用类属性_some_condition
class Bar(models.Model): ... foo = models.ForeignKey(Foo) _some_condition = False # 类属性 def save(self, *args, **kwargs): # 根据某些条件设置`_some_condition`类属性 self.foo.some_function(self._some_condition) super(Bar, self).save(*args, **kwargs)
这里要注意:
- 如果你只是读取
self._some_condition,那所有Bar实例默认都会用类里的False。 - 如果你写了
self._some_condition = True,那只是给当前实例创建了一个实例属性,其他实例还是用类的False。 - 如果你写了
Bar._some_condition = True,那所有Bar实例(没有自己同名实例属性的)都会变成True,这是全局共享的。
写法2:用实例属性_some_condition
class Bar(models.Model): ... def __init__(self, *args, **kwargs): super(Bar, self).__init__(*args, **kwargs) self._some_condition = False # 实例属性 ...
这种写法下,每个Bar实例都会有自己独立的_some_condition:
- 新建实例时会初始化,从数据库加载的实例也会在
__init__里初始化这个属性。 - 修改某个实例的
self._some_condition完全不会影响其他实例。
给你的建议
如果_some_condition是每个Bar实例自己的状态(比如和当前Bar记录相关的某个临时条件),一定要用实例属性的写法,这样不会出现不同实例互相干扰的问题。
只有当你需要一个所有Bar实例共享的全局状态时,才考虑用类属性,但要注意并发场景下的竞争问题(比如多个请求同时修改类属性,可能导致数据不一致)。
内容的提问来源于stack exchange,提问作者darkhorse
相关产品推荐
相关产品推荐

