Yes absolutely. More reason to free developers from fear of using metaclasses.
By using the metaclass wrapper I posted in the Help forum, it is able to pass your test case since it calls __init_subclass__
to register the class object only after the dataclass
decorator returns the new class.
As for passing on kwargs
it’s certainly a problem as well, and I have to work around it by storing it in a _dataclass_kwargs
attribute to pass it on:
_dataclass_keywords = set((code := dataclass.__code__).co_varnames[
code.co_argcount:code.co_argcount + code.co_kwonlyargcount])
class DataclassMeta(type):
def __new__(metacls, name, bases, namespace, **kwargs):
class InitSubclassBlocker:
def __init_subclass__(cls, **kwargs):
pass
dataclass_kwargs = namespace.get('_dataclass_kwargs') or {
key: kwargs[key] for key in kwargs.keys() & _dataclass_keywords}
configured_dataclass = dataclass(**dataclass_kwargs)
cls = super().__new__(
metacls, name, (InitSubclassBlocker,) + bases, namespace, **kwargs)
if classcell := namespace.get('__classcell__'):
cls.__classcell__ = classcell
cls._dataclass_kwargs = dataclass_kwargs
if 'slots' not in dataclass_kwargs or '__slots__' not in namespace:
cls = configured_dataclass(cls)
if hasattr(cls, '__classcell__'):
del cls.__classcell__
del InitSubclassBlocker.__init_subclass__
super(cls, cls).__init_subclass__(**kwargs)
return cls
and with the helper class Dataclass
now inheriting from a base class that defines a __init_subclass__
to remove known dataclass keywords:
class DataclassBase:
def __init_subclass__(cls, **kwargs):
for key in kwargs.keys() & _dataclass_keywords:
del kwargs[key]
super().__init_subclass__(**kwargs)
@dataclass_transform()
class Dataclass(DataclassBase, metaclass=DataclassMeta):
pass
Your test case woutld then output True
:
class RegistryClass:
registry = defaultdict(list)
def __init_subclass__(cls, **kwargs):
if (key := kwargs.pop('key', None)) is not None:
cls.registry[key].append(cls)
super().__init_subclass__(**kwargs)
class SlottedDataclass(RegistryClass, Dataclass, key="slotted", slots=True):
a: int
print(RegistryClass.registry['slotted'][0] is SlottedDataclass) # outputs True
Demo here
And a full demo of your PlayingCards
test case mixing RegistryClass
, Dataclass
and Enum
with the proposed combine_meta
here
class Card(RegistryClass, Dataclass, key="slotted", slots=True):
value_: Literal[2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'Q', 'K', 'A']
suite: Literal['heart', 'diamond', 'club', 'spade']
class PlayingCards(Card, Enum, metaclass=combine_meta):
TWO_OF_HEART = 2, 'heart'
THREE_OF_SPADES = 3, 'spade'
print(list(PlayingCards)) # outputs [<PlayingCards.TWO_OF_HEART: value_=2, suite='heart'>, <PlayingCards.THREE_OF_SPADES: value_=3, suite='spade'>]
print(PlayingCards.__slots__) # outputs ('value_', 'suite')
print(PlayingCards.registry['slotted']) # outputs [<class '__main__.Card'>]
Things should just work like putting together building blocks.