Search code examples
pythoninheritancepython-internals

How to interpret the error message "Foo() takes no arguments" when specifying a class instance as base class?


The following code:

>>> class Foo: pass
>>> class Spam(Foo()): pass

will of course raise an error message:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Foo() takes no arguments

But the error info is a little bit odd. Seems that there are some processes when initializing class Spam.

I wonder which step causes the error. In other words, why can't class inherit instance, the error message seems to indicate it indeed try something.


Note: I know there will be no error if write like this class Spam(Foo). But I do it on purpose. I just can't understand the error message, which indicates some procedures exist when class inheriting, I want to know which one procedure causes it.


Solution

  • Using a few dirty print statements we can get the sequence of events that is happening:

    class Foo:
        def __init__(self, *args, **kwargs):
            print("__init__", self, args, kwargs)
    
        def __new__(cls, *args, **kwargs):
            print("__new__", cls, args, kwargs)
            return super().__new__(cls)
    
    inst = Foo()
    
    class Spam(inst): pass
    
    print(inst, Spam)
    

    outputs

    __new__ <class '__main__.Foo'> () {}
    __init__ <__main__.Foo object at 0x0000019FFB399550> () {}
    __new__ <class '__main__.Foo'> ('Spam', (<__main__.Foo object at 0x0000019FFB399550>,), {'__module__': '__main__', '__qualname__': 'Spam'}) {}
    __init__ <__main__.Foo object at 0x0000019FFB399590> ('Spam', (<__main__.Foo object at 0x0000019FFB399550>,), {'__module__': '__main__', '__qualname__': 'Spam'}) {}
    <__main__.Foo object at 0x0000019FFB399550> <__main__.Foo object at 0x0000019FFB399590>
    

    So trying to inherit from an instance makes Python create a new instance of that class giving it parameters that would normally go to type.__new__.

    The reasons this happens is because types in python are objects: When deriving a class, python tries to get the correct meta class by calling type(base), which in this situation returns Foo (the class), and then it tries to create an instance of that. ( I could have sworn python checks that type(base) is a subclass of type, but apparently that is wrong)