Issue 29581: init_subclass causes TypeError when used with standard library metaclasses (such as ABCMeta) (original) (raw)

Created on 2017-02-16 18:54 by Nate Soares, last changed 2022-04-11 14:58 by admin. This issue is now closed.

Pull Requests
URL Status Linked Edit
PR 527 merged python-dev,2017-03-06 23:03
PR 1282 merged So8res,2017-04-25 16:19
Messages (9)
msg287966 - (view) Author: Nate Soares (Nate Soares) Date: 2017-02-16 18:54
I believe I've found a bug (or, at least, critical shortcoming) in the way that python 3.6's __init_subclass__ interacts with abc.ABCMeta (and, presumably, most other metaclasses in the standard library). In short, if a class subclasses both an abstract class and a class-that-uses-__init_subclass__, and the __init_subclass__ uses keyword arguments, then this will often lead to TypeErrors (because the metaclass gets confused by the keyword arguments to __new__ that were meant for __init_subclass__). Here's an example of the failure. This 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() raises this TypeError when run: Traceback (most recent call last): File "", line 10, in class Thingy(Abstracted, Initifier, x=1): TypeError: __new__() got an unexpected keyword argument 'x' See http://stackoverflow.com/questions/42281697/typeerror-when-combining-abcmeta-with-init-subclass-in-python-3-6 for further discussion.
msg288119 - (view) Author: Alyssa Coghlan (ncoghlan) * (Python committer) Date: 2017-02-19 08:06
This is going to be the case anytime an attempt is made to combine parent classes with incompatible constructor signatures. "type" is unusual in that it goes to great lengths to present an adaptive signature that aligns with whatever the class definition does. The main relevant trick is to filter the extra arguments out from those passed to metaclasses such that only init_subclass sees them: ================ def ignore_extra_args(base): base_meta = type(base) class _FilteredMeta(base_meta): def __new__(*args, **kwds): return base_meta.__new__(*args) def __init__(*args, **kwds): return base_meta.__init__(*args) class _Filtered(base, metaclass=_FilteredMeta): pass return _Filtered class InitX(): def __init_subclass__(cls, x=None): print('x') from abc import ABCMeta class Abstract(metaclass=ABCMeta): pass class AbstractWithInit(ignore_extra_args(Abstract), InitX, x=1): pass AbstractWithInit() ================ If folks were to iterate on "ignore_extra_args" variants outside the standard library with 3.6, then it would be something we could sensibly standardise for 3.7.
msg288255 - (view) Author: Kevin Shweh (Kevin Shweh) Date: 2017-02-21 03:05
Doesn't that ignore_extra_args thing prevent InitX.__init_subclass__ from receiving the x argument it wanted? It doesn't seem like a solution.
msg288450 - (view) Author: Alyssa Coghlan (ncoghlan) * (Python committer) Date: 2017-02-23 15:25
No, the filtering is only applied to the __new__ and __init__ calls on the metaclass, not to the __init_subclass__ call. Showing the last part of an interactive session where I ran the above commands: =========== >>> class AbstractWithInit(ignore_extra_args(Abstract), InitX, x=1): ... pass ... x >>> AbstractWithInit() <__main__.AbstractWithInit object at 0x7f9086694a58> >>> ===========
msg288451 - (view) Author: Alyssa Coghlan (ncoghlan) * (Python committer) Date: 2017-02-23 15:29
Oops, and now I see the typo in the example code, it seems you're right. I was misremembering an earlier more decorator-like variant of the design where we didn't rely on passing the __init_subclass__ arguments through the metaclass constructor.
msg288453 - (view) Author: Alyssa Coghlan (ncoghlan) * (Python committer) Date: 2017-02-23 15:38
Note that the publisher side workaround for this is relatively straightforward: __init_subclass__ implementations that want to be compatible with arbitrary metaclasses will need to take any additional parameters as class attributes (which they may delete), rather than as class header keyword arguments. For 3.7 though, it probably makes sense to update abc.ABCMeta to pass along arbitrary keyword arguments based on the same rationale as used in PEP 487 to justify making this change for type itself: by default, type.__init_subclass__ will still complain about it, but if someone overrides __init_subclass__ to accept additional keyword arguments, doing so will just work.
msg290179 - (view) Author: Łukasz Langa (lukasz.langa) * (Python committer) Date: 2017-03-24 22:18
New changeset bd583ef9857d99f9145ad0bb2c4424cc0baa63fc by Łukasz Langa (Nate) in branch 'master': bpo-29581: Make ABCMeta.__new__ pass **kwargs to type.__new__ (#527) https://github.com/python/cpython/commit/bd583ef9857d99f9145ad0bb2c4424cc0baa63fc
msg295312 - (view) Author: Mariatta (Mariatta) * (Python committer) Date: 2017-06-07 00:31
New changeset 6fb12b5c43945f61f3da82e33eafb4ddae2296ee by Mariatta (Nate) in branch '3.6': bpo-29581: bpo-29581: Make ABCMeta.__new__ pass **kwargs to type.__new__ (GH-527) (GH-1282) https://github.com/python/cpython/commit/6fb12b5c43945f61f3da82e33eafb4ddae2296ee
msg296165 - (view) Author: Alyssa Coghlan (ncoghlan) * (Python committer) Date: 2017-06-16 08:17
Thanks for the work on resolving this, all!
History
Date User Action Args
2022-04-11 14:58:43 admin set github: 73767
2017-06-16 08:17:14 ncoghlan set status: pending -> closedresolution: fixedmessages: + stage: backport needed -> resolved
2017-06-15 14:07:57 serhiy.storchaka set status: open -> pending
2017-06-07 00:31:05 Mariatta set nosy: + Mariattamessages: +
2017-04-25 16:19:26 So8res set pull_requests: + <pull%5Frequest1390>
2017-04-24 21:14:58 Mariatta set stage: backport needed
2017-03-28 04:57:53 xiang.zhang link issue29923 superseder
2017-03-24 22🔞28 lukasz.langa set nosy: + lukasz.langamessages: +
2017-03-06 23:03:43 python-dev set pull_requests: + <pull%5Frequest437>
2017-02-23 15:38:23 ncoghlan set messages: + versions: + Python 3.7
2017-02-23 15:30:17 ncoghlan set nosy: + Martin.Teichmann
2017-02-23 15:29:23 ncoghlan set messages: +
2017-02-23 15:25:36 ncoghlan set messages: +
2017-02-21 03:05:42 Kevin Shweh set nosy: + Kevin Shwehmessages: +
2017-02-19 08:06:13 ncoghlan set nosy: + ncoghlanmessages: +
2017-02-17 17:28:25 levkivskyi set nosy: + levkivskyi
2017-02-16 18:54:07 Nate Soares create