tags:

views:

169

answers:

4

I've been trying to understand Python's handling of class and instance variables. In particular, I found this answer quite helpful. Basically it says that if you declare a class variable, and then you do an assignment to [instance].property, you will be assigning to a different variable altogether -- one in a different namespace from the class variable.

So then I considered -- if I want every instance of my class to have a member with some default value (say zero), should I do it like this:

class Foo:
    num = 0

or like this?

class Foo:
    def __init__(self):
        self.num = 0

Based on what I'd read earlier, I'd think that the second example would be initializing the 'right' variable (the instance instead of the class variable). However, I find that the first method works perfectly well too:

class Foo:
    num = 0

bar = Foo()
bar.num += 1 # good, no error here, meaning that bar has an attribute 'num'
bar.num
>>> 1
Foo.num
>>> 0 # yet the class variable is not modified! so what 'num' did I add to just now?

So.. why does this work? What am I not getting? FWIW, my prior understanding of OOP has come from C++, so explanation by analogy (or pointing where it breaks down) might be useful.

A: 
bar.num += 1

creates a new instance variable 'num' on the 'bar' instance because it doesn't yet exist (and then adds 1 to this value)

an example:

class Foo:
  def __init__(self):
    self.num= 1

bar = Foo()
print bar.num

this prints 1

print bar.foo

this gives an error as expected: Traceback (most recent call last): File "", line 1, in AttributeError: Foo instance has no attribute 'foo'

bar.foo = 5
print bar.foo

now this prints 5

so what happens in your example: bar.num is resolved as Foo.num because there's only an class attribute. then foo.num is actually created because you assign a value to it.

Stefan De Boey
uh... `bar.foo += 1` also gives an error, so clearly Python doesn't create instance variables whenever it encounters them in an expression containing '+='. My question is, how does creating class variables lead to the initialization of the corresponding instance variables?
int3
@int3. This pertains to how attributes are resolved. When access an attribute, the object is searched first (`num` in this case), then the object's type (`Foo`) in this case, then the base classes of the object's type and their bases. If no matching attribute is found `AttributeError` is raised.
MattH
indeed, and then the instance variable is created because we assign a value to it. i'll update the example
Stefan De Boey
A: 

I think you just found a bug in Python there. bar.num += 1 should be an error, instead, it is creating an attribute num in the object bar, distinct from Foo.num.

It's a really strange behavior.

Morgaelyn
@Morgaelyn: No this is not a bug, this is intended behaviour. Read up on python classes http://docs.python.org/tutorial/classes.html
MattH
As MattH says, this intended and documented behavior. So, no, it's not a bug.
krawyoti
Now I have understood. b.num += 1 is the same as b.num = b.num + 1. The first b.num refers to the object attribute and is created. The second b.num refers actually to Foo.num and is 0. NOW that makes sense. I still uphold that this is a design error in the language. It's easy to assume that b.num in b.num += 1 refers always to the same variable, but it actually refers to two unrelated variables at the same time.
Morgaelyn
+2  A: 

Personally, I've found these documents by Shalabh Chaturvedi extremely useful and informative regarding this subject matter.

bar.num += 1 is a shorthand for bar.num = bar.num + 1. This is picking up the class variable Foo.num on the righthand side and assigning it to an instance variable bar.num.

MattH
And when I'm saying class / instance `variable` what I really mean is `attribute`.
MattH
+1  A: 

In the following code, num is a class member.

class Foo:
    num = 0

A C++ equivalent would be something like

struct Foo {
  static int num;
};

int Foo::num = 1;

class Foo:
    def __init__(self):
        self.num = 0

self.num is an instance member (self being an instance of Foo).

In C++, it would be something like

struct Foo {
  int num;
};

I believe that Python allows you to have a class member and an instance member sharing the same name (C++ doesn't). So when you do bar = Foo(), bar is an instance of Foo, so with bar.num += 1, you increment the instance member.

Bertrand Marron
Your C++ equivalent appears to have incremented the value of num by 1! :)
Kylotan