Following my rant regarding how dataclasses.dataclass
being a class decorator rather than a metaclass-powered class is preventing __init_subclass__
from accessing fields of a dataclass in a response to @MegaIng, I set out to implement exactly that, a metaclass that is really a thin wrapper to the dataclass
decorator with all of its capabilities while calling __init_subclass__
only after the new class has been transformed by dataclass
.
The goal was largely achieved by:
- inserting a dummy base class with a no-op
__init_subclass__
to block any__init_subclass__
of the rest of the base classes from being executed during the creation of the new class, and, - deleting
__init_subclass__
of the dummy base class after the new class is dataclass-transformed, so that, - calling
__init_subclass__
of the super class of the new class can then follow the intended MRO
from dataclasses import dataclass, fields
from typing import dataclass_transform
@dataclass_transform()
class DataclassMeta(type):
def __new__(metacls, name, bases, namespace, **kwargs):
class InitSubclassBlocker:
def __init_subclass__(cls, **kwargs):
pass
configured_dataclass = dataclass(**kwargs)
for key in ('slots', 'frozen'): # TODO: pop all known dataclass keywords
kwargs.pop(key, None)
cls = configured_dataclass(super().__new__(
metacls, name, (InitSubclassBlocker,) + bases, namespace, **kwargs))
del InitSubclassBlocker.__init_subclass__
super(cls, cls).__init_subclass__(**kwargs)
return cls
class Dataclass(metaclass=DataclassMeta):
pass
so that I can have a base class that processes dataclass fields when subclassed:
class PrintFields(Dataclass):
def __init_subclass__(cls, **kwargs):
for field in fields(cls):
print(field.name, field.type)
super().__init_subclass__(**kwargs)
class Foo(PrintFields):
foo: str
which correctly outputs:
foo <class 'str'>
But then I tried enabling the slots
option for PrintFields
:
class PrintFields(Dataclass, slots=True):
def __init_subclass__(cls, **kwargs):
for field in fields(cls):
print(field.name, field.type)
super().__init_subclass__(**kwargs)
And got:
Traceback (most recent call last):
File "/ATO/code", line 21, in <module>
class PrintFields(Dataclass, slots=True):
TypeError: __class__ set to <class '__main__.PrintFields'> defining 'PrintFields' as <class '__main__.PrintFields'>
Demo here
I’ve never seen this error before, and a look at the CPython source that produces this error message tells me that it has something to do with __classcell__
. With a bit of a search for that keyword, I found an open CPython issue (originated from this issue) regarding this symptom, but am still unable to comprehend from the responses the actual cause of this issue, and how I can possibly work around it.
Any insight would be appreciated.