10

I'm still fairly new to Python, and my OO experience comes from Java. So I have some code I've written in Python that's acting very unusual to me, given the following code:

class MyClass():
        mylist = []
        mynum = 0

        def __init__(self):
                # populate list with some value.
                self.mylist.append("Hey!")
                # increment mynum.

                self.mynum += 1

a = MyClass()

print a.mylist
print a.mynum

b = MyClass()

print b.mylist
print b.mynum

Running this results in the following output:

['Hey!']
1
['Hey!', 'Hey!']
1

Clearly, I would expect the class variables to result in the same exact data, and the same exact output... What I can't seem to find anywhere is what makes a list different than say a string or number, why is the list referencing the same list from the first instantiation in subsequent ones? Clearly I'm probably misunderstanding some kind of scope mechanics or list creation mechanics..

Russ
  • 103
  • 6

3 Answers3

8

tlayton's answer is part of the story, but it doesn't explain everything.

Add a

print MyClass.mynum

to become even more confused :). It will print '0'. Why? Because the line

self.mynum += 1

creates an instance variable and subsequently increases it. It doesn't increase the class variable.

The story of the mylist is different.

self.mylist.append("Hey!")

will not create a list. It expects a variable with an 'append' function to exist. Since the instance doesn't have such a variable, it ends up referring the one from the class, which does exist, since you initialized it. Just like in Java, an instance can 'implicitly' reference a class variable. A warning like 'Class fields should be referenced by the class, not by an instance' (or something like that; it's been a while since I saw it in Java) would be in order. Add a line

print MyClass.mylist

to verify this answer :).

In short: you are initializing class variables and updating instance variables. Instances can reference class variables, but some 'update' statements will automagically create the instance variables for you.

Confusion
  • 16,256
  • 8
  • 46
  • 71
  • This still misses the key point; see @Ken's answer. `self.mynum += 1` expands to `self.mynum = self.mynum + 1`. When this is executed, first the right-hand side is evaluated. `self.mynum` looks up `mynum` on the instances, and fails to find it (at least the first time `__init__` is called), so then looks up (and finds) `mynum` on the class. So the `self.mynum` on the right-hand side evaluates to the value of `MyClass.mynum`. That value is now assigned to `self.mynum`, which here refers to the instance variable. – Mark Dickinson Jul 01 '10 at 13:18
5

What you are doing here is not just creating a class variable. In Python, variables defined in the class body result in both a class variable ("MyClass.mylist") and in an instance variable ("a.mylist"). These are separate variables, not just different names for a single variable.

However, when a variable is initialized in this way, the initial value is only evaluated once and passed around to each instance's variables. This means that, in your code, the mylist variable of each instance of MyClass are referring to a single list object.

The difference between a list and a number in this case is that, like in Java, primitive values such as numbers are copied when passed from one variable to another. This results in the behavior you see; even though the variable initialization is only evaluated once, the 0 is copied when it is passed to each instance's variable. As an object, though, the list does no such thing, so your append() calls are all coming from the same list. Try this instead:

class MyClass():
    def __init__(self):
        self.mylist = ["Hey"]
        self.mynum = 1

This will cause the value to be evaluated separately each time an instance is created. Very much unlike Java, you don't need the class-body declarations to accompany this snippet; the assignments in the __init__() serve as all the declaration that is needed.

tlayton
  • 1,757
  • 1
  • 11
  • 13
  • 1
    "These are separate variables, not just different names for a single variable." <- I don't think this is true. Try doing `id(a.mylist)` and `id(MyClass.mylist)` and see what you get. – Mark Dickinson Jun 30 '10 at 20:35
  • They are separate variables, making the important distinction between variables and values. these two calls to id() will return equal, because both a.mylist and MyClass.mylist refer to the same object (created upon evaluation of the line "mylist = []", but they are still separate; assigning a completely different list to one of them later will not change the value of the other. – tlayton Jun 30 '10 at 20:41
  • @Mark: You are right that it isn't true. The first two lines in the class create class variables and only class variables. See my answer for the correct story. However, your method to show that it isn't true doesn't work: the instance can reference the class variable and those two commands will in fact return the same id. However, when repeated for the 'mynum' variable, they will return different id's. – Confusion Jun 30 '10 at 20:44
  • @Confusion: I see what you are saying. The issue at hand is whether the instance variable is created along with the instance, or the first time that it is assigned. I suspect that you may actually be right about this (it makes a bit more sense, conceptually), but I can't think of any tests to determine this distinction. Any suggestions? – tlayton Jun 30 '10 at 20:56
  • Upon further investigation, I have found a suitable test case: changing the value of the class variable after creating the instance causes the instance variable to change as well. I concede that Confusion is correct. – tlayton Jun 30 '10 at 20:58
5

I believe the difference is that += is an assignment (just the same as = and +), while append changes an object in-place.

    mylist = []
    mynum = 0

This assigns some class variables, once, at class definition time.

    self.mylist.append("Hey!")

This changes the value MyClass.mylist by appending a string.

    self.mynum += 1

This is the same as self.mynum = self.mynum + 1, i.e., it assigns self.mynum (instance member). Reading from self.mynum falls through to the class member since at that time there is no instance member by that name.

Ken
  • 9,797
  • 3
  • 16
  • 14
  • Technically, MyClass.mylist is not a value. Like self.mylist, it is a variable. Calling self.mylist.append() does not change the value of MyClass.mylist; it alters the list to which both variable refer. – tlayton Jun 30 '10 at 20:37
  • +1. Nice answer. @tlayton: What do you mean when you say 'value'? I'd certainly say that calling `self.mylist.append("Hey!")` alters the value of `MyClass.mylist`. – Mark Dickinson Jun 30 '10 at 20:58
  • Not according to my understanding of the meaning of "value" in this context. The value of MyClass.mylist is the list to which it refers. calling append() does not cause mylist to refer to a different list, but rather alters the list itself. I'm probably interpreting "value" a little too technically, though. – tlayton Jun 30 '10 at 21:02