4

I found that some classes contain a __init__ function, and some don’t. I’m confused about something described below.

What is the difference between these two pieces of code:

class Test1(object):
    i = 1

and

class Test2(object):
    def __init__(self):
        self.i = 1

I know that the result or any instance created by these two class and the way of getting their instance variable are pretty much the same. But is there any kind of “default” or “hidden” initialization mechanism of Python behind the scene when we don’t define the __init__ function for a class? And why I can’t write the first code in this way:

class Test1(object):
    self.i = 1

That’s my questions. Thank you very much!


Thank you very much Antti Haapala! Your answer gives me further understanding of my questions. Now, I understand that they are different in a way that one is a "class variable", and the other is a "instance variable". But, as I tried it further, I got yet another confusing problem.

Here is what it is. I created 2 new classes for understanding what you said:

class Test3(object):
    class_variable = [1]
    def __init__(self):
        self.instance_variable = [2]

class Test4(object):
    class_variable = 1
    def __init__(self):
        self.instance_variable = 2

As you said in the answer to my first questions, I understand the class_variable is a "class variable" general to the class, and should be passed or changed by reference to the same location in the memory. And the instance_variable would be created distinctly for different instances.

But as I tried out, what you said is true for the Test3's instances, they all share the same memory. If I change it in one instance, its value changes wherever I call it.

But that's not true for instances of Test4. Shouldn't the int in the Test4 class also be changed by reference?

i1 = Test3()

i2 = Test3()
>>> i1.i.append(2)
>>> i2.i
[1, 2]

j1 = Test4()
j2 = Test4()
>>> j1.i = 3
>>> j2.i
1

Why is that? Does that "=" create an "instance variable" named "i" without changing the original "Test4.i" by default? Yet the "append" method just handles the "class variable"?

Again, thank you for your exhaustive explanation of the most boring basic concepts to a newbie of Python. I really appreciate that!

Z_J
  • 125
  • 1
  • 11

2 Answers2

4

In python the instance attributes (such as self.i) are stored in the instance dictionary (i.__dict__). All the variable declarations in the class body are stored as attributes of the class.

Thus

class Test(object):
    i = 1

is equivalent to

class Test(object):
    pass
Test.i = 1

If no __init__ method is defined, the newly created instance usually starts with an empty instance dictionary, meaning that none of the properties are defined.

Now, when Python does the get attribute (as in print(instance.i) operation, it first looks for the attribute named i that is set on the instance). If that fails, the i attribute is looked up on type(i) instead (that is, the class attribute i).

So you can do things like:

class Test:
    i = 1

t = Test()
print(t.i)  # prints 1
t.i += 1
print(t.i)  # prints 2

but what this actually does is:

>>> class Test(object):
...     i = 1
... 
>>> t = Test()
>>> t.__dict__
{}
>>> t.i += 1
>>> t.__dict__
{'i': 2}

There is no i attribute on the newly created t at all! Thus in t.i += 1 the .i was looked up in the Test class for reading, but the new value was set into the t.

If you use __init__:

>>> class Test2(object):
...     def __init__(self):
...         self.i = 1
... 
>>> t2 = Test2()
>>> t2.__dict__
{'i': 1}

The newly created instance t2 will already have the attribute set.


Now in the case of immutable value such as int there is not that much difference. But suppose that you used a list:

class ClassHavingAList():
    the_list = []

vs

class InstanceHavingAList()
    def __init__(self):
        self.the_list = []

Now, if you create 2 instances of both:

>>> c1 = ClassHavingAList()
>>> c2 = ClassHavingAList()
>>> i1 = InstanceHavingAList()
>>> i2 = InstanceHavingAList()
>>> c1.the_list is c2.the_list
True
>>> i1.the_list is i2.the_list
False
>>> c1.the_list.append(42)
>>> c2.the_list
[42]

c1.the_list and c2.the_list refer to the exactly same list object in memory, whereas i1.the_list and i2.the_list are distinct. Modifying the c1.the_list looks as if the c2.the_list also changes.

This is because the attribute itself is not set, it is just read. The c1.the_list.append(42) is identical in behaviour to

getattr(c1, 'the_list').append(42)

That is, it only tries read the value of attribute the_list on c1, and if not found there, then look it up in the superclass. The append does not change the attribute, it just changes the value that the attribute points to.

Now if you were to write an example that superficially looks the same:

c1.the_list += [ 42 ]

It would work identical to

original = getattr(c1, 'the_list')
new_value = original + [ 42 ]
setattr(c1, 'the_list', new_value)

And do a completely different thing: first of all the original + [ 42 ] would create a new list object. Then the attribute the_list would be created in c1, and set to point to this new list. That is, in case of instance.attribute, if the attribute is "read from", it can be looked up in the class (or superclass) if not set in the instance, but if it is written to, as in instance.attribute = something, it will always be set on the instance.


As for this:

class Test1(object):
    self.i = 1

Such thing does not work in Python, because there is no self defined when the class body (that is all lines of code within the class) is executed - actually, the class is created only after all the code in the class body has been executed. The class body is just like any other piece of code, only the defs and variable assignments will create methods and attributes on the class instead of setting global variables.

  • Thank you very much Mr. Antti Haapala. That's really brilliant as a demonstration. But as I tried to test a little bit, I got yet another confusing problem. Could you please also help me with that? I added that part to my original questions. @Antti Haapala – Z_J Feb 19 '15 at 10:51
  • I got the reason of my newly added question now! Thank you! – Z_J Feb 19 '15 at 11:37
0

I understood my newly added question. Thanks to Antti Haapala.

Now, when Python does the get attribute (as in print(instance.i) operation, it first looks for the attribute named i that is set on the instance). If that fails, the i attribute is looked up on type(i) instead (that is, the class attribute i).

I'm clear about why is:

j1 = Test4()
j2 = Test4()
>>> j1.i = 3
>>> j2.i
1

after few tests. The code

j1.3 = 3

actually creates a new instance variable for j1 without changing the class variable. That's the difference between "=" and methods like "append".

I'm a newbie of Python coming from c++. So, at the first glance, that's weird to me, since I never thought of creating a new instance variable which is not created in the class just using the "=". It's really a big difference between c++ and Python.

Now I got it, thank you all.

Z_J
  • 125
  • 1
  • 11
  • Ah also, if you have C++ background, you should think it this way: *all* the variables, attributes and so on in Python are like C++ pointers. They never contain the actual objects themselves. – Antti Haapala -- Слава Україні Feb 19 '15 at 11:49
  • Even integers, in Python if you write `x = 3`, in CPython the implementation code does [`PyObject *x = PyLong_FromLong(3);`](https://docs.python.org/3/c-api/long.html#c.PyLong_FromLong). The assignment statement changes whatever these named pointers are pointing to. – Antti Haapala -- Слава Україні Feb 19 '15 at 11:53
  • Correct me if I'm wrong. It's pretty much close to the concept stated in Swift - "classes are reference type", right? Thanks you very much! – Z_J Feb 19 '15 at 11:58
  • I am not familiar at all with Swift, but in Objective-C yes all classes are by reference (`NSObject *`), it is the same in Python except *everything* is always by reference. – Antti Haapala -- Слава Україні Feb 19 '15 at 12:05
  • Great to know that everything is always by reference. Again, I really appreciate your help! It's really a wonderful experience for me as this is my first question in stackoverflow community and it's been answered much more exhaustive than I expected. – Z_J Feb 19 '15 at 12:12