16

I'm trying to use python 3.6's new __init_subclass__ feature (PEP 487) with the abc module. It doesn't seem to be working. The following code:

from abc import ABCMeta
class Initifier:
    def __init_subclass__(cls, x=None, **kwargs):
        super().__init_subclass__(**kwargs)
        print('got x', x)

class Abstracted(metaclass=ABCMeta):
    pass

class Thingy(Abstracted, Initifier, x=1):
    pass

thingy = Thingy()

yields the following when run:

Traceback (most recent call last):
  File "<filename>", line 10, in <module>
    class Thingy(Abstracted, Initifier, x=1):
TypeError: __new__() got an unexpected keyword argument 'x'

Everything works fine if Abstracted doesn't use the ABCMeta metaclass.

This error is fairly resilient, for example, the following code still fails with a similar type error (presumably because a metaclass' __new__ runs at class instantiation time, whereas the parent class' __new__ doesn't run until object instantiation).

from abc import ABCMeta

class Initifier:
    def __new__(cls, name, bases, dct, x=None, **kwargs):
        return super().__new__(cls, name, bases, dct, **kwargs)

    def __init_subclass__(cls, x=None, **kwargs):
        super().__init_subclass__(**kwargs)
        print('got x', x)

class Abstracted(metaclass=ABCMeta):
    pass

class Thingy(Initifier, Abstracted, x=1):
    pass

thingy = Thingy()

Can anyone confirm that this is a bug in the Python 3.6 abc module and/or __init_subclass__ implementation? (I might be using __init_subclass__ wrong.) Does anyone have a workaround?

So8res
  • 9,856
  • 9
  • 56
  • 86
  • 6
    That's a funny interaction in the new `__init_subclass__` design. Pretty much every metaclass in existence is now supposed to pass unexpected keyword arguments through to `super().__new__` so `type.__new__` can pass them to `__init_subclass__`, but ABCMeta and probably tons of other metaclasses don't do that. – user2357112 Feb 16 '17 at 18:25
  • That's probably going to be a headache for a while for anyone who wants to use `__init_subclass__` together with a metaclass they don't control. – user2357112 Feb 16 '17 at 18:27
  • I expect that this works if you reverse the order of inheritance, correct? Since `Initifier` will eat `x` before `Abstracted` sees it. – Rick Feb 16 '17 at 18:31
  • @RickTeachey incorrect, alas. It breaks with the same TypeError regardless of inheritance order. – So8res Feb 16 '17 at 18:32
  • 1
    huh. Well that sucks. – Rick Feb 16 '17 at 18:33
  • What if you reverse the order and add a call to `__new__` in `Initifier` and eat the `x` argument in there? – Rick Feb 16 '17 at 18:35
  • That also fails, surprisingly. (I'll edit the code into the OP momentarily.) – So8res Feb 16 '17 at 18:38
  • 1
    No nevermind it isn't surprising because the exception happens upon instantiation of the class, not a class instance. – Rick Feb 16 '17 at 18:39

1 Answers1

12

It's a bug in abc.ABCMeta, due to a wart in the design of __init_subclass__. I recommend reporting it.

Pretty much every metaclass in existence is now supposed to pass unexpected keyword arguments through to super().__new__ so type.__new__ can pass them to __init_subclass__, but ABCMeta and probably tons of other metaclasses don't do that yet. abc.ABCMeta.__new__ chokes on the x keyword argument instead of passing it through, causing the exception you see.

Trying to use __init_subclass__ keyword arguments with a metaclass that hasn't been updated for the new design isn't going to work. You'll have to wait for the metaclasses you use to be patched.

user2357112
  • 260,549
  • 28
  • 431
  • 505