Python奇怪的类变量用法

时间:2012-07-10 21:59:29

标签: python google-app-engine python-2.7 class-variables

假设我们有以下代码:

class A:
    var = 0
a = A()

明白a.varA.var是不同的变量,我想我明白为什么会发生这种情况。我认为这只是python数据模型的副作用,因为为什么有人想修改实例中的类变量?

然而,今天我遇到了一个类似用法的奇怪例子:它位于谷歌应用引擎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")

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

6 个答案:

答案 0 :(得分:3)

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

title="The Three Little Pigs"

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

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

这就是变量按照它们的方式设置的原因,以便保持关系。

编辑:让我更好地描述一下。普通类只是为对象设置蓝图。它有实例变量和类变量。由于db.Model的继承性,这实际上是做第三件事:在数据库中设置列定义。为了完成这个第三项任务,它将使EXTENSIVE背后的场景变化,如属性设置和获取。几乎从db.Model继承后,你不再是一个类了,而是一个DB模板。长话短说,这是一个使用类

的非常具体的边缘情况

答案 1 :(得分:1)

如果所有变量都声明为instance variables,那么使用classes类作为Story的{​​{1}}将继承

答案 2 :(得分:1)

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

如果你要问故事.__ dict __ ['title'],它会给你带来什么?

答案 3 :(得分:1)

  

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

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

在Python中,您在class块中声明的所有内容都属于该类。如果实例上没有具有该名称的内容,您可以通过实例查找类的属性。当分配给实例的属性时,实例现在具有该属性,无论之前是否有该属性。 (__init__,在这方面,只是另一个功能;它由Python的机器自动调用,但它只是将属性添加到一个对象,它没有神奇地指定某种类型该类的所有实例的内容的模板 - 那里有魔术__slots__类属性,但它仍然没有达到你所期望的那样。)

但是现在,a没有自己的.var,因此a.var指的是A.var。您可以通过实例修改类属性 - 但请注意修改,而不是替换。当然,这要求属性的原始值是可修改的 - list符合条件,str没有。

然而,你的GAE例子完全不同。类Story具有特别属于"属性"的属性,当你"分配给"时,它可以做各种魔术。他们。这是通过使用课程' __getattr____setattr__等方法可以更改赋值语法的行为。

答案 4 :(得分:1)

其他答案大部分都是正确的,但却错过了一件至关重要的事情。

如果您定义这样的类:

class Foo(object):
  a = 5

和一个实例:

myinstance = Foo()

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

这也有助于解释为什么赋值不会以您期望的方式工作,因为变量的共享性质:

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

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

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

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

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

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

__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

描述符是令人惊讶的Python语言背后的秘密。例如,class Foo(object): a = MultiplyDescriptor(2) bar = Foo() bar.a = 10 print bar.a # Prints 20! 是使用描述符实现的,方法,静态和类方法以及一堆其他东西也是如此。

答案 5 :(得分:0)

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

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

>>> class A:
...     var = 0
... 
... a = A()
... A.var = 3
... a.var == A.var
1: True