views:

78

answers:

6

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!

+6  A: 

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
A: 

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
A: 

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
A: 
>>> 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
A: 

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
A: 

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