7

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.

Community
  • 1
  • 1
int3
  • 12,861
  • 8
  • 51
  • 80

5 Answers5

5

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
  • 37,273
  • 11
  • 82
  • 84
2

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
  • 21,501
  • 8
  • 58
  • 94
0

Searching for this very question, both this StackOverflow question and two (rather old, but valid) slides by Guido van Rossum (1, 2) came up high. Guido's slides state this behavior has to do with Python's search order for accessing the attribute (in the case of the example above num). Thought it'd be nice to put the two together right here :)

akaIDIOT
  • 9,171
  • 3
  • 27
  • 30
0
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
  • 2,344
  • 16
  • 14
  • 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 Mar 11 '10 at 11:40
  • 1
    @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 Mar 11 '10 at 11:54
  • indeed, and then the instance variable is created because we assign a value to it. i'll update the example – Stefan De Boey Mar 11 '10 at 11:58
-3

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.

Thiago Chaves
  • 9,218
  • 5
  • 28
  • 25
  • @Morgaelyn: No this is not a bug, this is intended behaviour. Read up on python classes http://docs.python.org/tutorial/classes.html – MattH Mar 11 '10 at 12:00
  • As MattH says, this intended and documented behavior. So, no, it's not a bug. – krawyoti Mar 11 '10 at 13:15
  • 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. – Thiago Chaves Mar 11 '10 at 13:27
  • Not a design error. It's an implementation detail. You usually don't shadow a class attribute with an instance attribute. – Jürgen A. Erhard Sep 11 '12 at 23:39