Required kwarg in __prepare__

Hi I am attempt to learn the class creation process better. I am interested in the aspect of class parameterization. Why do I get an error if I make a required key word argument in __prepare__? If I just use the default value, the class is defined properly but if i explicitly pass it, I get an error in __init_subclass__?

In [13]: class Meta(type):
    ...:     @classmethod
    ...:     def __prepare__(mcs, *args, required_kwarg="foo", **kwargs):
    ...:         print(required_kwarg)
    ...:         return {}

In [14]: class Foo(metaclass=Meta):
    ...:     ...
foo

In [15]: class Foo2(metaclass=Meta, required_kwarg="foo2"):
    ...:     ...
foo2
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Input In [15], in <module>
----> 1 class Foo2(metaclass=Meta, required_kwarg="foo2"):
      2     ...

TypeError: Foo2.__init_subclass__() takes no keyword arguments

If I remove the default value to required_kwarg then __prepare__ raises an error as I would expect but I am still getting the __init_subclass__ error when trying to create Foo2

In [19]: class Meta(type):
    ...:     @classmethod
    ...:     def __prepare__(mcs, *args, required_kwarg, **kwargs):
    ...:         print(required_kwarg)
    ...:         return {}

In [20]: class Foo(metaclass=Meta):
    ...:     ...
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Input In [20], in <module>
----> 1 class Foo(metaclass=Meta):
      2     ...

TypeError: Meta.__prepare__() missing 1 required keyword-only argument: 'required_kwarg'

In [21]: class Foo2(metaclass=Meta, required_kwarg="foo2"):
    ...:     ...
foo2
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Input In [21], in <module>
----> 1 class Foo2(metaclass=Meta, required_kwarg="foo2"):
      2     ...

TypeError: Foo2.__init_subclass__() takes no keyword arguments

It seems like passing any kind of keyword argument through the class definition throws an error with __init_subclass__.

In [1]: class Meta(type):
   ...:     @classmethod
   ...:     def __prepare__(mcs, *args, **kwargs):
   ...:         if "required_kwarg" not in kwargs:
   ...:             raise TypeError('missing "required_kwarg" keyword argument')
   ...:         else:
   ...:             required_kwarg = kwargs["required_kwarg"]
   ...:         print(required_kwarg)
   ...:         return {}

In [2]: class Foo2(metaclass=Meta, required_kwarg="foo2"):
   ...:     ...
foo2
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Input In [2], in <module>
----> 1 class Foo2(metaclass=Meta, required_kwarg="foo2"):
      2     ...

TypeError: Foo2.__init_subclass__() takes no keyword arguments

I’ve been researching this more off and on and this is like the closest explanation I come of it. I don’t think its a direct cause, as I am using python 3.10, but could it be a similar problem?

https://bugs.python.org/issue29581

You can avoid the issue by also defining a __new__ method that removes the arguments before passing them to type.__new__:

class Meta(type):
    def __prepare__(mcs, *args, required_kwarg, **kwargs):
        print(required_kwarg)
        return {}

    def __new__(mcs, *args, required_kwarg, **kwargs):
        return super().__new__(mcs, *args, **kwargs)
1 Like

I see.

Interestingly I ran this in python 3.5, which is right before PEP487 with __init_subclass__ and I get a different error

>>> class Meta(type):
...     @classmethod
...     def __prepare__(mcs, *args, required_kwarg="foo", **kwargs):
...             print(required_kwarg)
...             return {}
...
>>> class Foo2(metaclass=Meta, required_kwarg="foo2"):
...     ...
...
foo2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: type() takes 1 or 3 arguments

But I see this section PEP487 hinting at this sort of behavior. Glad I’ve finally figured this one out