pythonclassinstance-variablesclass-variables

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


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?


Solution

  • 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.