I hope this information gives you a better understanding of why we can call our classes, where do those arguments go, where are the class's parameters and etc.
So, let's talk about __call__
:
Think about implementing __call__
inside your class. It enables the instance of the class to be callable in other words, "this" is the reason you can put ()
after your instance right ? And when you call your instances this way, __call__
is the actual method that is getting called ! You could pass arguments when you are calling the instance if you define them in the __call__
method as parameters.
class A:
def __call__(self, arg):
print('It is calling', arg)
instance = A()
instance('foo')
Now we know that classes themselves are instances --> of type type
:
class A:
pass
print(isinstance(A, type)) # True
So there has to be a __call__
method in "their" class(better to say metaclass) and of course there is one in type
. That's the reason we can put ()
after the class to call it and pass arguments to it.
When you put parentheses in front of you class, you are actually calling this __call__
method of the class type
with the given arguments here arg1
, arg2
...
Wait, But where is __init__
? where is __new__
? How do they get called ?
In fact they get called inside the __call__
method of the class type
with exactly those arguments you passed. First __new__
, if it returns an instance of the class then __init__
gets called like a normal instance method.
Now I'm going to demonstrate what I explained before: (The default metaclass of the all classes is type
class, but I'm going to use mine to show you the details)
class Mymeta(type):
def __call__(cls, *args, **kwargs):
print(f"Metaclass's __call__ gets called with: {args} and {kwargs}")
new_created_instance = cls.__new__(cls, *args, **kwargs)
if isinstance(new_created_instance, cls):
new_created_instance.__init__(*args, **kwargs)
return new_created_instance
class A(metaclass=Mymeta):
def __new__(cls, *args, **kwargs):
# return 10 # <-- If you uncomment this, __init__ won't get called.
return super(A, cls).__new__(cls)
def __init__(self, name, **kwargs):
print(
'This is printed only because'
' we called it inside `__call__` method of the meta class'
)
self.name = name
obj = A('soroush', age=5)
output :
Metaclass's __call__ gets called with: ('soroush',) and {'age': 5}
This is printed only because we called it inside `__call__` method of the meta class
Note two things, first, All the arguments that we passed in front of our class is passed directly to the __call__
method of the metaclass. Second, as you can see __call__
of the Mymeta
is the caller for both __new__
and __init__
.