1

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()
S.B
  • 13,077
  • 10
  • 22
  • 49
Tms91
  • 3,456
  • 6
  • 40
  • 74
  • 4
    No, your first snippet is correct. The 'parameters' to a class are other classes you want to inherit from. Exercise: How do you instantiate `my_class`? – quamrana Aug 10 '21 at 13:56
  • 2
    ``My_Class(arg1, arg2 ...etc).method_1()`` also instantiates the class. Then it calls a method on the instance and throws the instance a way afterwards. – MisterMiyagi Aug 10 '21 at 14:04

3 Answers3

6

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

S.B
  • 13,077
  • 10
  • 22
  • 49
  • While I know what you are aiming at, I think this isn't helpful unless actually writing out what ``type.__call__`` does. – MisterMiyagi Aug 10 '21 at 14:15
  • @quamrana In OP he mentioned "How can the arguments arg1, arg2 ...etc be passed to my_class if the class has no parameters" , I was trying to explain the rationale how these parameters are getting passed. – S.B Aug 10 '21 at 14:16
  • @MisterMiyagi You're right, I'm going to add more information – S.B Aug 10 '21 at 14:18
  • @quamrana calling an instance was just the introduction. The concept is the same. you can put parenthesis in front of your "any" object if you implement `__call__` in it's type ! – S.B Aug 10 '21 at 14:21
  • Ok, I see where you are going with this. – quamrana Aug 10 '21 at 14:22
  • Looks ok. I'm not an expert in python meta class, but I'm sure I've seen code like this which instantiates an object, then calls `__init__()` on the object and returns it. – quamrana Aug 10 '21 at 15:05
  • 1
    It's probably worth adding that ``__init__`` is only called if ``__new__`` returns an instance of ``cls``. – MisterMiyagi Aug 10 '21 at 15:41
2

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.

quamrana
  • 37,849
  • 12
  • 53
  • 71
  • ok, I maybe get it. First: I am instantiating the class but not saving that instance, Second: I don't need to specify the arguments of the `__init__` method in the class arguments (where the class is defined) as well, right? – Tms91 Aug 10 '21 at 14:10
  • 2
    First, you *are* saving the instance implicitly, but only long enough to make the call to `method_1()`. Second the syntax of instantiating a class *looks* like you are giving the class some parameters, whereas python applies those parameters to the `__init__()` method. – quamrana Aug 10 '21 at 14:13
  • Ok, I was just wondering how could the arguments be passed to the `__init__` method if the class was declared as "with zero arguments", but ok, I now understand how things works. Thanks! – Tms91 Aug 10 '21 at 14:17
1

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.

MisterMiyagi
  • 44,374
  • 10
  • 104
  • 119