【问题标题】:Python and object/class attrs - what's going on?Python 和对象/类属性 - 发生了什么?
【发布时间】:2011-03-03 23:32:42
【问题描述】:

有人可以解释为什么 Python 会执行以下操作吗?

>>> class Foo(object):
...   bar = []
...
>>> a = Foo()
>>> b = Foo()
>>> a.bar.append(1)
>>> b.bar
[1]
>>> a.bar = 1
>>> a.bar
1
>>> b.bar
[1]
>>> a.bar = []
>>> a.bar
[]
>>> b.bar
[1]
>>> del a.bar
>>> a.bar
[1]

这很混乱!

【问题讨论】:

  • 真的吗?很容易看出,如果有人花时间使用几乎任何其他 OOP 语言,他们会如何期望 bar 成为本地实例变量。
  • @Oli:没时间上 Python 教程?
  • @SilentGhost:如果我们开始挑选人们提出可以通过阅读文档来回答的问题,那么甚至不会有 SO。
  • @danben:教程没有简单回答的许多 SO 问题。然而,遗憾的是,我们必须通过提供教程链接来回答问题。如果他们先阅读本教程,那将会更有趣。
  • @digitala:你用的是哪个教程?请提供名称或链接。

标签: python class-attributes


【解决方案1】:

这是因为你写它的方式,bar 是一个类变量而不是一个实例变量。

要定义一个实例变量,在构造函数中绑定它:

class Foo(object):
  def __init__(self):
    self.bar = []

请注意,它现在属于 Foo (self) 的单个实例,而不是 Foo 类,您将在分配给它时看到预期的结果。

【讨论】:

    【解决方案2】:

    正如其他人所说,编写的代码创建了一个类变量而不是实例变量。您需要在__init__ 中赋值才能创建实例变量。

    希望这个带注释的代码副本有助于解释每个阶段的情况:

    >>> class Foo(object):
    ...   bar = []          # defines a class variable on Foo (shared by all instances)
    ...
    >>> a = Foo()
    >>> b = Foo()
    >>> a.bar.append(1)     # appends the value 1 to the previously empty list Foo.bar
    >>> b.bar               # returns the value of the class variable Foo.bar
    [1]
    >>> a.bar = 1           # binds 1 to the instance variable a.bar, masking the access
    >>> a.bar               # you previously had to the class variable through a.bar
    1
    >>> b.bar               # b doesn't have an instance variable 'bar' so this still
    [1]                     # returns the class variable
    >>> a.bar = []          # bind a's instance variable to to an empty list
    >>> a.bar
    []
    >>> b.bar               # b doesn't have an instance variable 'bar' so this still
    [1]                     # returns the class variable
    >>> del a.bar           # unbinds a's instance variable unmasking the class variable
    >>> a.bar               # so a.bar now returns the list with 1 in it.
    [1]
    

    此外,在您的每条语句之后打印出Foo.bar(通过类而不是通过实例访问的类变量)的值可能有助于澄清发生了什么。

    【讨论】:

      【解决方案3】:

      当你在类中声明一个元素时,它被类的所有实例共享。要创建属于每个实例的适当类成员,请分别在 __init__ 中创建它,如下所示:

      class Foo(object):
          def __init__(self):
              self.bar = []
      

      【讨论】:

        【解决方案4】:

        一开始bar是一个类变量,ab共享,a.barb.bar指的是同一个对象。

        当您为a.bar 分配新值时,这不会覆盖类变量,它会向a 对象添加一个新的实例变量,当您访问a.bar 时隐藏类变量。如果您删除a.bar(实例变量),那么a.bar 将再次解析为类变量。

        另一方面,b.bar 总是引用类变量,它不受 a 对象上的附加 bar 或分配给它的任何值的影响。

        要设置类变量,您可以通过类本身访问它:

        Foo.bar = 1
        

        【讨论】:

          【解决方案5】:
          >>> class Foo(object):
          ...   bar = []
          ...
          

          bar 是共享类变量,而不是实例变量。我相信这可以解决您的大部分困惑。要使其成为实例变量,请根据其他答案在类的 __init__ 中定义它。

          >>> a = Foo()
          >>> b = Foo()
          >>> a.bar.append(1)
          >>> b.bar
          [1]
          

          这就是证明。

          >>> a.bar = 1
          >>> a.bar
          1
          >>> b.bar
          [1]
          

          现在您已将a.bar 重新定义为实例变量。这就是默认情况下在外部定义变量时发生的情况。

          >>> a.bar = []
          >>> a.bar
          []
          >>> b.bar
          [1]
          >>> del a.bar
          >>> a.bar
          [1]
          

          还是一样。 b.bar 仍然是共享类变量。

          【讨论】:

            【解决方案6】:

            在相关的说明中,您应该注意这个您可能很快就会看到的陷阱:

            class A:
               def __init__(self, mylist = []):
                  self.mylist = mylist
            
            
            a = A()
            a2 = A()
            
            a.mylist.append(3)
            print b.mylist #prints [3] ???
            

            这让很多人感到困惑,并且与代码的解释方式有关。 Python 实际上首先解释函数标题,因此它评估 __init__(self, mylist = []) 并将对该列表的引用存储为默认参数。这意味着 A 的所有实例(除非提供自己的列表)都将引用原始列表。做这种事情的正确代码是

            class A:
               def __init__(self, mylist=None):
                  if mylist:
                     self.mylist = mylist
                  else:
                     self.mylist = []
            

            或者如果你想要一个更短的表达式,你可以使用三元语法:

            self.mylist = mylist if mylist else []
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2014-02-03
              • 2012-10-08
              • 2017-09-27
              • 1970-01-01
              • 2012-01-31
              • 2013-03-28
              • 1970-01-01
              相关资源
              最近更新 更多