【问题标题】:Python weird class variables usagePython奇怪的类变量用法
【发布时间】:2012-07-10 11:35:06
【问题描述】:

假设我们有以下代码:

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")

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

【问题讨论】:

  • 只要它在内部引用self.var而不是CLASSNAME.var,那么它只是修改自己实例的值,有效地成为实例变量......
  • @JoranBeasley 是的,它们的行为确实像实例变量,那么为什么不让它们成为实例呢? :) 类变量看起来太多了。
  • 注意 appengine 使用元类。您的类中的属性定义正在创建 Property 对象的实例,这些实例管理从数据存储中获取的数据到实例化对象的映射。这与其他基于 ORM 的库(如 SQLAlchemy、Storm 等)使用的方法相同。如果您想了解更多有关元类和元编程的信息,请顺其自然。

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


【解决方案1】:

如果所有变量都声明为instance variables,那么使用Story 类作为superclassclasses继承任何内容。

【讨论】:

  • 但继承的类可能会继承并调用超类上的 __init__
【解决方案2】:

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

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

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

【讨论】:

  • ... 但a.var = 5; a.var==A.var 返回False
  • @HughBothwell 是的,因为这样可以覆盖变量。
  • 我宁愿避免在这里使用术语“覆盖”——它有一个非常具体的、不相关的含义——相反,你创建了一个隐藏类变量的对象变量。
【解决方案3】:

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

title="The Three Little Pigs"

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

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

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

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

【讨论】:

  • 如果标题是实例变量,那是不是不可能做到这一点?
  • 编辑:不,让我更好地描述一下。普通类只是为对象设置蓝图。它有实例变量和类变量。由于继承了 db.Model,这实际上是在做第三件事:在数据库中设置列定义。为了完成第三项任务,它在幕后对属性设置和获取等内容进行了广泛的更改。几乎一旦你从 db.Model 继承,你就不再是一个真正的类,而是一个数据库模板。长话短说,这是使用类的一个非常特殊的边缘案例。
  • 那么,你的意思和卢克一样吗?谢谢,我终于明白了:)我会将您的答案标记为正确,您能否将您的评论与答案正文合并?
  • 如果你很好奇,你应该阅读 python 元类——用于创建其他类的类,它在 db.Model 的幕后使用。 stackoverflow.com/questions/100003/…
【解决方案4】:

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

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

【讨论】:

  • 哦,我想我明白了。您的意思是直接访问包装到相应属性中的值,而不是执行类似 story.title = db.StringProperty(title) 之类的操作?
  • 无论如何,这是我的怀疑。我想我必须看看源代码才能确定;文档对实现不是很清楚。
  • 不,没有必要使用__getattr____setattr__;模型属性是描述符。
【解决方案5】:

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

首先:到目前为止,不,他们不是

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

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

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

【讨论】:

  • 不,使用类的__getattr____setattr__ 不起作用,它通过使用描述符起作用。
【解决方案6】:

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

如果你这样定义一个类:

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 时,它修改了Foo 的所有实例在您请求instance.a 时通常解析的变量。但是当我们分配给bar.a 时,Python 在该实例上创建了一个名为a 的新变量,它现在掩盖了类变量 - 从现在开始,该特定实例将始终看到它自己的本地值。

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

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

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

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

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 是使用描述符实现的,方法、静态和类方法以及其他一些东西也是如此。

【讨论】:

    猜你喜欢
    • 2019-01-28
    • 1970-01-01
    • 1970-01-01
    • 2022-10-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-25
    • 1970-01-01
    相关资源
    最近更新 更多