2

假设我们有以下代码:

class A:
    var = 0
a = A()

确实理解这一点a.var并且A.var是不同的变量,我想我理解为什么会发生这种情况。我认为这只是 python 数据模型的副作用,因为为什么有人要修改实例中的类变量?

然而,今天我遇到了这样一个奇怪的例子:它在 google app engine db.Model reference 中。Google 应用引擎数据存储假设我们继承db.Model类并将键作为类变量引入:

class Story(db.Model):
    title = db.StringProperty()
    body = db.TextProperty()
    created = db.DateTimeProperty(auto_now_add=True)
s = Story(title="The Three Little Pigs")

我不明白他们为什么希望我这样做?为什么不引入构造函数并仅使用实例变量?

4

6 回答 6

3

db.Model 类是经典模型视图控制器设计模式中的“模型”样式类。那里的每个作业实际上都是在数据库中设置列,同时还提供了一个易于使用的界面供您进行编程。这就是为什么

title="The Three Little Pigs"

将更新对象以及数据库中的列。

有一个构造函数(毫无疑问在 db.Model 中)处理这个传递逻辑,它会接受一个关键字 args 列表并消化它来创建这个关系模型。

这就是变量按原样设置的原因,以便保持这种关系。

编辑:让我更好地描述一下。普通类只是为对象设置蓝图。它有实例变量和类变量。由于继承了 db.Model,这实际上是在做第三件事:在数据库中设置列定义。为了完成第三项任务,它在幕后对属性设置和获取等内容进行了广泛的更改。几乎一旦你从 db.Model 继承,你就不再是一个真正的类,而是一个数据库模板。长话短说,这是一个非常特殊的使用类的边缘案例

于 2012-07-10T22:08:28.313 回答
1

我明白 a.var 和 A.var 是不同的变量

首先:截至目前,不,他们不是

在 Python 中,您在class块中声明的所有内容都属于该类。如果实例还没有具有该名称的东西,您可以通过实例查找类的属性。当您分配给实例的属性时,该实例现在具有该属性,而不管它之前是否具有。( __init__,在这方面,只是另一个函数;它由 Python 的机器自动调用,但它只是为对象添加属性,它并没有神奇地为类的所有实例的内容指定某种模板——这就是魔法__slots__class 属性,但它仍然没有达到您的预期。)

但眼下,a没有.var自己的,所以a.varA.var。您可以通过实例修改类属性 - 但请注意修改,而不是替换。当然,这要求属性的原始值是可修改的——a 有list资格,astr没有。

但是,您的 GAE 示例完全不同。该类Story具有专门为“属性”的属性,当您“分配给”它们时,它们可以发挥各种魔力。这通过使用 class'__getattr____setattr__方法来改变赋值语法的行为。

于 2012-07-10T22:59:04.343 回答
1

从模型和属性文档来看,模型似乎覆盖了 __getattr__ 和 __setattr__ 方法,因此实际上“Story.title = ...”实际上并没有设置实例属性;相反,它设置与实例的属性一起存储的值。

如果你要求 story.__dict__['title'],它会给你什么?

于 2012-07-10T22:11:57.097 回答
1

其他答案大多是正确的,但错过了一件关键的事情。

如果你定义一个这样的类:

class Foo(object):
  a = 5

和一个例子:

myinstance = Foo()

然后Foo.amyinstance.a是相同的变量。更改一个将更改另一个,如果您创建多个实例Foo,则.a每个实例的属性将是相同的变量。这是因为 Python 解析属性访问的方式:首先它在对象的字典中查找,如果在那里找不到,它在类的字典中查找,依此类推。

这也有助于解释考虑到变量的共享性质,为什么赋值不能按您期望的方式工作:

>>> bar = Foo()
>>> baz = Foo()
>>> Foo.a = 6
>>> bar.a = 7
>>> bar.a
7
>>> baz.a
6

这里发生的情况是,当我们分配给时,它修改了所有实例在您请求时通常解析Foo.a的变量。但是当我们分配给 时,Python 在该实例上创建了一个名为 的新变量,它现在掩盖了类变量 - 从现在开始,该特定实例将始终看到它自己的本地值。Fooinstance.abar.aa

如果你希望你的类的每个实例都有一个单独的变量初始化为 5,那么通常的方法是这样的:

class Foo(object);
  def __init__(self):
    self.a = 5

也就是说,您定义了一个带有构造函数的类,该构造函数将a新实例上的变量设置为 5。

最后,App Engine 正在做的是一种完全不同的黑魔法,称为描述符。简而言之,Python 允许对象定义特殊的__get____set__方法。当定义这些特殊方法的类的实例附加到一个类,并且您创建该类的实例时,尝试访问该属性将不是设置或返回实例或类变量,而是调用特殊__get____set__方法. 可以在这里找到更全面的描述符介绍,但这里有一个简单的演示:

class MultiplyDescriptor(object):
  def __init__(self, multiplicand, initial=0):
    self.multiplicand = multiplicand
    self.value = initial

  def __get__(self, obj, objtype):
    if obj is None:
      return self
    return self.multiplicand * self.value

  def __set__(self, obj, value):
    self.value = value

现在您可以执行以下操作:

class Foo(object):
  a = MultiplyDescriptor(2)

bar = Foo()
bar.a = 10
print bar.a # Prints 20!

描述符是大量 Python 语言背后的秘密武器。例如,property使用描述符实现,方法、静态方法和类方法以及一堆其他东西也是如此。

于 2012-07-27T07:48:14.640 回答
1

如果所有变量都被声明为,instance variables那么classes使用Story类 assuperclass将不会从它继承任何内容。

于 2012-07-10T22:04:40.727 回答
0

这些类变量是 Google App Engine 生成其模型的元数据。

仅供参考,在您的示例中,a.var == A.var.

>>> class A:
...     var = 0
... 
... a = A()
... A.var = 3
... a.var == A.var
1: True
于 2012-07-10T22:05:32.817 回答