apostofes
apostofes

Reputation: 3693

Unaccessible `__init__`

class MetaA(type):
  def __new__(self, clsname, bases, clsdict):
    print('meta __new__ called')
    return super().__new__(self, clsname, bases, clsdict)

  def __call__(self, *args, **kwargs):
    print('meta __call__ called')
class A(metaclass=MetaA):
  def __init__(self, x):
    print('__init__ called')
    self.x = x
meta __new__ called
a = A(['1'])
meta __call__ called
type(a)
NoneType

It appears that specifying a __call__ in the metaclass, blocks the __init__ of the main class. How do I access the __init__ of the main class?

Upvotes: 0

Views: 69

Answers (1)

Blckknght
Blckknght

Reputation: 104702

Your Meta.__call__ method doesn't return anything, which is the same as returning None. That's why a is None at the end of your code.

The default behavior of type.__call__ (which you're overriding) is to create an instance and return it. If you still want that to happen, you need to call (and return the result of) super().__call__(*args, **kwargs), similar to what you do in Meta.__new__.

def __call__(self, *args, **kwargs):
  print('meta __call__ called')
  return super().__call__(*args, **kwargs)

I suppose you could instead reimplement the behavior of type.__call__ (e.g. by calling the __new__ and __init__ methods of the class yourself), but unless you want to change some part of the default behavior, calling type.__call__ to do it via super() is easier.

It's unrelated to your issue, but the first argument name you're using in your metaclass methods may lead you astray at some point. The __new__ method is called as if it was a classmethod, with the metaclass as its first argument. The __call__ method on the other hand is a normal method, being called on an instance of the metaclass, which is a class. I'd use meta (or maybe mcls) and cls as the argument names, respectively, to avoid confusion. I reserve self for regular classes, to refer to their own instances.

I guess if you wanted to reimplement type.__call__, you could use self to name the instance after you've created it:

def __call__(cls, *args, **kwargs):
  print('meta __call__ called')

  self = cls.__new__(cls, *args, **kwargs) # create the instance with __new__

  if isinstance(self, cls):                # if __new__ didn't do something strange
    self.__init__(*args, **kwargs)         # call __init__ on the instance

  return self                              # then return it to the caller

Upvotes: 1

Related Questions