1

Can someone explain why Python does the following?

>>> 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]

It's rather confusing!

SilentGhost
  • 307,395
  • 66
  • 306
  • 293
Phillip B Oldham
  • 18,807
  • 20
  • 94
  • 134
  • 2
    Really? It's easy to see how somebody would expect `bar` to be a local instance variable if they'd spent any time in almost any other OOP language. – Oli Jun 15 '10 at 13:03
  • 1
    @Oli: and no time in Python tutorial? – SilentGhost Jun 15 '10 at 13:05
  • 1
    @SilentGhost: if we would start picking on people for asking questions which could have been answered by reading the documentation, there wouldn't even be an SO. – danben Jun 15 '10 at 13:13
  • 1
    @danben: Many SO questions that are not trivially answered by the tutorials. Sadly, however, we have to answer questions by providing links to the tutorial. If they would read the tutorial first, SO would be far, far more interesting. – S.Lott Jun 15 '10 at 13:31
  • @digitala: Which tutorial are you using? Please provide the name or a link. – S.Lott Jun 15 '10 at 13:32
  • @S.Lott - I do agree, but think that trying to achieve a site like that would be a Sisyphean task. – danben Jun 15 '10 at 13:56
  • @danben: "a site like that"? You mean no questions trivially answered by the tutorials? It's obviously impossible to achieve that kind of "perfection". However, it's easy to direct people to the tutorials and not waste a lot of time on their questions. Downvoting helps. Pointing out duplicates helps. – S.Lott Jun 15 '10 at 14:34
  • @S.Lott: no tutorial - having problems with some supplied code, unit tests were confusing, code made use of lots of class instances, but then some would get "changed" for instances. Wanted to get a definitive, plain-English answer. Where better than SO?! :) – Phillip B Oldham Jun 16 '10 at 08:59
  • @digitala: no tutorial. Amazing. Just diving into code. Okay, that explains the confusion. You can't learn the language from bad code. Please find a tutorial. – S.Lott Jun 16 '10 at 13:16

6 Answers6

7

This is because the way you have written it, bar is a class variable rather than an instance variable.

To define an instance variable, bind it in the constructor:

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

Note that it now belongs to a single instance of Foo (self) rather than the Foo class, and you will see the results you expect when you assign to it.

danben
  • 80,905
  • 18
  • 123
  • 145
0

When you declare an element in the class like that it is shared by all instances of the class. To make a proper class member that belongs to each instance, separately, create it in __init__ like the following:

class Foo(object):
    def __init__(self):
        self.bar = []
Michael Aaron Safyan
  • 93,612
  • 16
  • 138
  • 200
0

In the beginning, bar is a class variable and it is shared between a and b, both a.bar and b.bar refer to the same object.

When you assign a new value to a.bar, this does not overwrite the class variable, it adds a new instance variable to the a object, hiding the class variable when you access a.bar. If you delete a.bar (the instance variable), then a.bar resolves again to the class variable.

b.bar on the other hand always refers to the class variable, it's not influenced by the additional bar on the a object or any values assigned to that.

To set the class variable you can access it through the class itself:

Foo.bar = 1
sth
  • 222,467
  • 53
  • 283
  • 367
0
>>> class Foo(object):
...   bar = []
...

bar is a shared class variable, not an instance variable. I believe that deals with most of your confusion. To make it a instance var, define it in class's __init__ per the other answers.

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

This is the proof of that.

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

Now you've redefined a.bar as a instance variable. That's what happens when you define variables externally by default.

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

Same again. b.bar is still the shared class variable.

Oli
  • 235,628
  • 64
  • 220
  • 299
0

As others have said the code as written creates a class variable rather than an instance variable. You need to assign in __init__ to create an instance variable.

Hopefully this annotated copy of your code is helpful in explaining what's going on at each stage:

>>> 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]

Also, printing out the value of Foo.bar (the class variable accessed via the class rather than via an instance) after each of your statements might help clarify what is going on.

mikej
  • 65,295
  • 17
  • 152
  • 131
0

On a related note, you should be aware of this pitfall that you might see sometime soon:

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


a = A()
a2 = A()

a.mylist.append(3)
print b.mylist #prints [3] ???

This confuses a lot of folks and has to do with how the code is interpreted. Python actually interprets the function headings first, so it evaluates __init__(self, mylist = []) and stores a reference to that list as the default parameter. That means that all instances of A will (unless provided their own list) reference the original list. The correct code for doing such a thing would be

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

or if you want a shorter expression you can use the ternary syntax:

self.mylist = mylist if mylist else []
Wayne Werner
  • 49,299
  • 29
  • 200
  • 290