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