How objects are created
Objects other than built-in types or compiled module classes are created at runtime. Objects can be classes, instances, functions, and so on. We call an object's type to give us a new instance; or put in another way, we call a type
class to give us an instance of that type.
Creation of function objects
Key 5: Create function on runtime.
Let's first take a look at how function objects can be created. This will broaden our view. This process is done by interpreter behind the scenes when it sees a def
keyword. It compiles the code, which is shown as follows, and passes the code name arguments to the function class that returns an object:
>>> function_class = (lambda x:x).__class__ >>> function_class <class 'function'> >>> def foo(): ... print("hello world") ... >>> >>> def myprint(*args,**kwargs): ... print("this is my print") ... print(*args,**kwargs) ... >>> newfunc1 = function_class(foo.__code__, {'print':myprint}) >>> newfunc1() this is my print hello world >>> newfunc2 = function_class(compile("print('asdf')","filename","single"),{'print':print}) >>> newfunc2() asdf
Creation of instances
Key 6: Process flow for instance creation.
We call class to get a new instance. We saw from the making calls to objects section that when we call class, it calls its metaclass __call__
method to get a new instance. It is the responsibility of __call__
to return a new object that is properly initialized. It is able to call class's __new__
, and __init__
because class is passed as first argument, and instance is created by this function itself:
>>> class Meta(type): ... def __call__(*args): ... print("meta call ",args) ... return None ... >>> >>> class C(metaclass=Meta): ... def __init__(*args): ... print("C init not called",args) ... >>> c = C() #init will not get called meta call (<class '__main__.C'>,) >>> print(c) None >>>
To enable developer access to both functionalities, creating new object, and initializing new object, in class itself; __call__
calls the __new__
class to return a new object and __init__
to initialize it. The full flow can be visualized as shown in the following code:
>>> class Meta(type): ... def __call__(*args): ... print("meta call ,class object :",args) ... class_object = args[0] ... if '__new__' in class_object.__dict__: ... new_method = getattr(class_object,'__new__',None) ... instance = new_method(class_object) ... else: ... instance = object.__new__(class_object) ... if '__init__' in class_object.__dict__: ... init_method = getattr(class_object,'__init__',None) ... init_method(instance,*args[1:]) ... return instance ... >>> class C(metaclass=Meta): ... def __init__(instance_object, *args): ... print("class init",args) ... def __new__(*args): ... print("class new",args) ... return object.__new__(*args) ... >>> class D(metaclass=Meta): ... pass ... >>> c=C(1,2) meta call ,class object : (<class '__main__.C'>, 1, 2) class new (<class '__main__.C'>,) class init (1, 2) >>> d = D(1,2) meta call ,class object : (<class '__main__.D'>, 1, 2) >>>
Take a look at the following diagram:
Creation of class objects
Key 7: Process flow for class creation.
There are three ways in which we can create classes. One is to simply define the class. The second one is to use the built-in __build_class__
function, and the third is to use the new_class
method of type
module. Method one uses two, method two uses method three internally. When interpreter sees a class keyword, it collects the name, bases, and metaclass that is defined for the class. It will call the __build_class__
built-in function with function (with the code object of the class), name of the class, base classes, metaclass that is defined, and so on:
__build_class__(func, name, *bases, metaclass=None, **kwds) -> class
This function returns the class. This will call the __prepare__
class method of metaclass to get a mapping data structure to use as a namespace. The class body will be evaluated, and local variables will be stored in this mapping data structure. Metaclass's type will be called with this namespace dictionary, bases, and class name. It will in turn call the __new__
and __init__
methods of metaclass. Metaclass can change attributes passed to its method:
>>> function_class = (lambda x:x).__class__ >>> M = __build_class__(function_class( ... compile("def __init__(self,):\n print('adf')", ... '<stdin>', ... 'exec'), ... {'print':print} ... ), ... 'MyCls') >>> m = M() adf >>> print(M,m) <class '__main__.MyCls'> <__main__.MyCls object at 0x0088B430> >>>
Take a look at the following diagram: