Tms91
Tms91

Reputation: 4154

How can the arguments get passed to the class if the class has no parameters?

I could be lacking of very basic python basis. I'm handling a script made by others.

I have a script, let's say My_Class, in which the code is:

import thing1
import thing_2
...etc

class My_Class:

    def __init__(self, arg1, arg2 ...etc):
        self.argument_1 = arg1
        self.argument_2 = arg2
        ...etc

Is it correct that my_class has __init__ whose arguments are not passed as arguments to my_class? (my_class does not have arguments at all)

How can the arguments arg1, arg2 ...etc be passed to my_class if the class has no parameters?

Shouldn't it be

import thing1
import thing_2
...etc

class My_Class(arg1, arg2 ...etc):

    def __init__(self, arg1, arg2 ...etc):
        self.argument_1 = arg1
        self.argument_2 = arg2
        ...etc

instead?

Note:

The class does not get instantiated, only its method are called, directly on the class, i.e:

My_Class(arg1, arg2 ...etc).method_1()

Upvotes: 1

Views: 1879

Answers (3)

S.B
S.B

Reputation: 16476

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

Upvotes: 7

MisterMiyagi
MisterMiyagi

Reputation: 50076

TLDR: The "signature" of a class is defined by its __init__ and/or __new__ method.


The class statement is an implicit call to the metaclass*. Any arguments of the class statement, including the bases, are consumed by the metaclass. The metaclass is responsible to use the class name, arguments and body to create the actual class object.

#              v arguments passed to the metaclass
class My_Class(arg1, arg2, ...):
    ...

Notably, these are the arguments passed to the metaclass. In common cases, these are just the base classes; passing or even expecting other arguments is only needed for advanced cases.

When calling a class, as in My_Class(arg1, arg2, ...), this passes the arguments to the class' __init__ and/or __new__. As such, __init__/__new__ define which parameters "a class" takes.

class My_Class:
#                      v parameters taken by the class
    def __init__(self, arg1, arg2 ...etc):
        self.argument_1 = arg1
        self.argument_2 = arg2

*This is a slight simplification of how metaclasses work. There are various levels of indirection that are not needed to understand the broad picture.

Upvotes: 1

quamrana
quamrana

Reputation: 39354

Your first snippet is correct:

class My_Class:

    def __init__(self, arg1, arg2 ...etc):
        self.argument_1 = arg1
        self.argument_2 = arg2
        ...etc

#Instantiation
c = My_Class(arg1, arg2 ...etc)
#Call method on instance
c.method_1()

You definitely are instantiating the class: My_Class(arg1, arg2 ...etc) is interpreted by python as:

  1. Instantiate an object.
  2. Call __init__() on that object (with arg1, arg2 ...etc in your case)

In python the __init__() method is considered synonymous with the term 'constructor' used in other languages.

Also note that in my snippet above the variable c hangs onto the instance unlike your snippet.

Upvotes: 2

Related Questions