Django和“普通”Python中类成员和实例成员的区别是什么?

8

这里有一个初学者问题!之前我问过这个问题:将CSV记录解析成类的列表,这个问题也在这里得到了更专业的回答:如何避免在实例之间共享类数据?

我了解到,在Python类中,将按对象定义的变量需要在__init__(self)函数中声明。

因此:

class ClassOne:
    def __init__(self, datetime):
        self.datetime = datetime
    v = []

变量v将为ClassOne的所有实例保存相同的数据,而对于:

class ClassTwo:
    def __init__(self, datetime):
        self.datetime = datetime
        self.v = []

变量v包含ClassTwo的每个实例的个人数据

然而,在Django中(我现在正在学习),我再次看到变量的“正常”(更像C++)行为:

class Post(models.Model):
    title = models.CharField(max_length = 255)

在这里,变量title存储Post每个实例的单独数据,尽管没有在__init__函数中定义。
我的基本问题是为什么或如何title各个类对象相关联,而不是像上面ClassOne中的v一样对每个类对象都是通用的?
如果我理解得正确,这意味着Django类的解释方式与普通Python类不同?然而,这个结论并不make sense...
我希望有人可以帮助我理解这一点。之前我认为,可以使用它的类和程序在Django应用中构建python代码(例如,数据分析或科学模型)成为基于web的服务。如果两个不同类的实现方式不同,那么这将非常困难!
可能已经在其他地方回答过了。我不太熟悉Django jango,所以不知道要搜索什么。

你可能想在 ClassTwo.__init__ 中设置 self.v = [];只有 v 是函数局部变量。 - Martijn Pieters
你说得对,我已经修复了。我还稍微澄清了一下问题:为什么titlev被处理的方式不同? - Swift Arrow
你为什么认为它会被区别对待?这与在类上设置任何其他属性没有区别;你可以直接引用那些在类上的属性。现在Post.title已经存在了。你可以枚举Post类上的所有属性,列表中将包括title。这就是重点 - Martijn Pieters
1
而且 title 属性也是共享的,但实例也有一个 title 属性。 - Martijn Pieters
4个回答

7
title属性并不是数据。它只是一个模型描述,一个描述title字段应该包含什么类型信息的对象。
因此,它是类定义的一部分;Post类的各个实例将具有符合类上的models.CharField()实例设置的约束条件的title属性。
您需要构建这样一个模型,以向Django描述如何构建表单字段以及如何为Post实例构建SQL表格;这两个概念需要比Python本身通常需要的更多类型信息。
每个Post实例也会被赋予一个title属性。然后该属性就遮盖了类属性。
p = Post(title='Some title')
print p.title  # prints 'Some title'

Python首先直接查找实例;如果没有title属性,则会将查找移到类对象上。但这里不需要,因为实例本身具有title属性,所以无需查找Post.title属性。

顺便说一下,在Python中,“数据”和方法之间没有绝对的区别。Python中的所有东西都是对象,包括类和方法。因此,在实例上查找属性时,也可以在其中找到一个对象,包括方法。如果在那里查找属性失败,那么Python将在类和基类上查找该属性,如果失败,则回退到元类。

这就是可变属性的作用;在实例上查找ClassOne().v失败,但在类上成功。然后操纵该列表将更改ClassOne.v类属性,并且再次查找其他实例上的v将再次找到类属性。这就是类属性共享的方式,就像类上的方法一样。


那么,理解为title属性是Post类内部的一个类实例是正确的吗?就像Toppings(一个带有变量如typeamount等的类)在Pizza类中使用一样?据我理解,Toppings(以及title)仍然需要在__init__函数内声明,对吗? - Swift Arrow
不,你不必这样做。你可以使用类属性来有意地共享数据或提供默认值。一旦在实例上设置了一个属性(例如self.v = []),同名的类属性就会被隐藏。因此,你可以在类上设置默认值,并在实例上覆盖特定属性。 - Martijn Pieters
我仍然不清楚为什么title不是类属性? - Swift Arrow
2
@SwiftArrow:它既是类属性又是实例属性。这是两个不同的属性,Python在查找时会找到其中一个。 - Martijn Pieters

4
作为对类变量和实例变量关系的更一般解释,考虑以下与Django模型无关的示例:
>>> class A(object):
...     x = 2
...     y = 1
...
...     def __init__(self):
...         self.x = 3
... 
>>> A.x
2
>>> instance = A()
>>> instance.x 
3
>>> instance.y
1
>>> instance.y = 4
>>> instance.y
4
>>> A.y
1

这里有两个值得注意的事情。首先,同名的类变量和实例变量是可以同时存在的。如果没有同名的实例变量,类变量只能从实例中直接访问。这就是Django模型的工作原理,类变量是字段(它们是实例变量的描述),而实例变量是特定实例的值。使用相同的名称用于类和实例变量可能会令人困惑,并且不是轻率的事情。在Django模型的情况下,我认为它非常有效,但仍然可能会引起一些问题(当我第一次使用Django时,我也有类似的问题)。

第二个需要注意的是,您可以在任何地方为实例分配变量,而不必在__init__函数或实例类的方法中进行。这并不意味着明确定义所有实例变量都在__init__函数中不是一个坏主意。在许多情况下,这是个好主意。


如果说 A.x = [],然后 instance.x.append(1),为什么 A.x 变成了 == [1]? - david valentino

4
Django并没有改变语言的规则。它创造性地使用了该语言。就像class ClassTwo(...): v = []创建了一个列表并将其存储在类中一样,class Post(...): title = something 创建了一个something并将其存储在类中。在这种情况下,所谓的something不是char字段值,如"foo",而是表示具有最大长度为255的char字段概念的对象。
Django收集这些表示数据库类型的对象,并创建(除其他外)一个__init__方法,使得Post实例具有相同名称的属性(该属性包含实际字符串值)。这个实现非常高级,但严格遵循Python语言的规则——你和我都可以创建自己的Python库来做类似的事情。无论如何,由于实例属性会覆盖类属性,因此您永远不会注意到Post.title仅存在一次并且实际上不是标题字符串。 a_post_object.title总是给你实例属性。

谢谢,这似乎让事情变得更清晰了一些...所以如果我理解正确的话,Django的models会处理它们继承的类并自动创建一个__init__函数,将实际的数据容器放在每个类中?可以说,如果我想要创建一个类属性,我可以在Django类定义中添加一个简单的数据变量吗? - Swift Arrow
1
@SwiftArrow:基类已经为您提供了__init__方法,无需自己生成。如果需要,您可以查看GitHub上的模型基类 - Martijn Pieters

0

这是一个非常老的帖子。我碰巧在刚接触Django时遇到了同样的问题。 'title'类是models.CharField,它似乎是一个Python描述符。根据描述符的定义,'title'是一个类变量。p.title = 'xxx',没有实例'title'。上面的语句调用类变量title来为实例创建一个隐藏的title。print(p.title)只会调用类变量title来返回实例的不可见title。

https://docs.python.org/3.7/howto/descriptor.html Python cookbook第8.6章也谈到了描述符。

希望我的回答正确并能帮到你。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接