Search code examples
pythonclassconstructorcallinit

__call__ or __init__ called here? Don't undestand which and why


Edit: this is unfortunately not answered in What is the difference between __init__ and __call__ in Python?

class OAuth2Bearer(requests.auth.AuthBase):

    def __init__(self, api_key, access_token):
        self._api_key = api_key
        self._access_token = access_token

    def __call__(self, r):
        r.headers['Api-Key'] = self._api_key
        r.headers['Authorization'] = "Bearer {}".format(self._access_token)
        return r

#############

class AllegroAuthHandler(object):
    def apply_auth(self):
        return OAuth2Bearer(self._api_key, self.access_token)   # what will happen here?

I read about __init__ and __call__, but I still don't undestand what is going on in this code

I don't understand:

1.) Which method will be called, __init__ or __call__

2.) If __init__, then __init__ doesn't return anything

3.) If __call__, then __call__ can't be called with two parameters

I think __init__ should be called, because we have X(), not x() from example below as in this answer:

x = X() # __init__ (constructor)
x() # __call__

Solution

  • I believe this is what you're looking for.

    The behaviour of calling an object in Python is governed by its type's __call__, so this:

    OAuth2Bearer(args)
    

    Is actually this:

    type(OAuth2Bearer).__call__(OAuth2Bearer, args)
    

    What is the type of OAuth2Bearer, also called its "metaclass"? If not type, the default, then a subclass of type (this is strictly enforced by Python). From the link above:

    If we ignore error checking for a minute, then for regular class instantiation this is roughly equivalent to:

    def __call__(obj_type, *args, **kwargs):
        obj = obj_type.__new__(*args, **kwargs)
        if obj is not None and issubclass(obj, obj_type):
            obj.__init__(*args, **kwargs)
        return obj
    

    So the result of the call is the result of object.__new__ after passed to object.__init__. object.__new__ basically just allocates space for a new object and is the only way of doing so AFAIK. To call OAuth2Bearer.__call__, you would have to call the instance:

    OAuth2Bearer(init_args)(call_args)