Python, how come we can create class variables that were not defined in class creation?

Question:

Let’s say we have this simple Python code

class MyClass(object):
    class_var = 1

    def __init__(self, i_var):
        self.i_var = i_var

Correct me if I get any of this wrong:

Class_Var is a class variable that is the same for all instances of MyClass object.
I_Var is an instance variable that only exists in instances of the MyClass object

foo = MyClass(2)
bar = MyClass(3)

foo.class_var, foo.i_var
## 1, 2
bar.class_var, bar.i_var
## 1, 3

Class variables are also properties of the class itself.

MyClass.class_var ##
## 1

MyClass.I_var should error out, correct?

Does that mean that class variables can be considered like instance variables of the class object itself (since all classes are objects) ?

MyClass.new_attribute = 'foo'
print(hasattr(ObjectCreator, 'new_attribute'))

That should return true. And

print (MyClass.new_attribute)

should return foo.

How come we can create a new class variable that was not defined in the original definition for that class?

Is

MyClass.new_attribute = 'foo'

the exact same thing as creating that class attribute in the original definition?

class MyClass(object):
    class_var = 1
    new_attribute = 'foo'

So we can create new class attributes at runtime? How does that not interfere with the init constructor that creates the class object and has those class variables as instance variables of the class object?

Asked By: user7959439

||

Answers:

A class object is just an instance of yet another type, usually type (though you can change this using the metaclass parameter to the class statement).

Like most other instances, you can add arbitrary instance attributes to a class object at any time.

Class attributes and instance attributes are wholly separate; the former are stored on the class object, the latter on instances of the class.

There’s nothing particularly special about __init__; it’s just another method that, among other things, can attached new attributes to an object. What is special is that __init__ is called automatically when you create a new instance of the class by calling the class. foo = MyClass(2) is equivalent to

foo = MyClass.__new__(MyClass, 2)
foo.__init__(2)

The class statement

class MyClass(object):
    class_var = 1

    def __init__(self, i_var):
        self.i_var = i_var

is roughly equivalent to

def my_class_init(self, i_var):
    self.i_var = i_var


MyClass = type('MyClass', (object,), {'class_var': 1, '__init__': my_class_init})

The 3-argument form of type lets you pass a dict that creates class attributes when you first create the class, but you can always assign attributes after the fact as well:

MyClass = type('MyClass', (object,), {})
MyClass.class_var = 1
MyClass.__init__ = my_class_init

Just to blow your mind a little bit more, the call to type can be thought of as

MyClass = type.__new__(type, 'MyClass', (object,), {...})
MyClass.__init__('MyClass', (object,), {...})

though unless you define a custom metaclass (by subclassing type), you never have to think about type itself having __new__ and __init__ methods.

Answered By: chepner

Does that mean that class variables can be considered like instance variables of the class object itself (since all classes are objects) ?

Yes.

How come we can create a new class variable that was not defined in the original definition for that class?

Because Python is a dynamic language. A class can be created at run time – in fact, it is created at run time when you run Python interactively.

So we can create new class attributes at runtime?

Yes, unless the metaclass (the class of the class) has forbidden it.

How does that not interfere with the init constructor that creates the class object and has those class variables as instance variables of the class object?

The only rule is that you cannot use something that has not yet be defined or something that has been deleted:

>>> class MyClass(object):
    class_var = 1

    def __init__(self, i_var):
        self.i_var = i_var
        self.j_var = self.class_var + 1


>>> a = MyClass(2)
>>> del MyClass.class_var
>>> b = MyClass(3)
Traceback (most recent call last):
  File "<pyshell#39>", line 1, in <module>
    b = MyClass(3)
  File "<pyshell#36>", line 6, in __init__
    self.j_var = self.class_var + 1
AttributeError: 'MyClass' object has no attribute 'class_var'

There is no magic here: anything can only exists between its definition point and its destruction point. Python allows you to add attributes to objects at any time, except that some classes (for example object) forbid it.

With the previous a object of class MyClass, you could do:

a.z_var = 12

from that point, z_var will be an attribute of a but others objects of same class will not have it.

Simply object forbids that:

>>> o = object()
>>> o.x=1
Traceback (most recent call last):
  File "<pyshell#41>", line 1, in <module>
    o.x=1
AttributeError: 'object' object has no attribute 'x'
Answered By: Serge Ballesta