biqqles / dataclassy

A fast and flexible reimplementation of data classes
https://pypi.org/project/dataclassy
Mozilla Public License 2.0
82 stars 9 forks source link

Metaclass Error #16

Closed TylerYep closed 3 years ago

TylerYep commented 3 years ago

I encountered this issue while testing dataclassy. From reading the documentation, it looks like I should pass something into the meta= parameter to dataclasses? I'm not quite sure how to fix this. If this is easily solvable, maybe you could edit to the error message to explain what to do.

This was while testing the performance branch, but I suspect it applies to the current main branch as well.

from typing import Any, Generic, Mapping, TypeVar
from dataclassy import dataclass
V = TypeVar("V")

@dataclass
class Edge(Generic[V], Mapping[str, Any]):
    start: V
    end: V
    weight: float

or a simpler version:

from typing import Mapping
from dataclassy import dataclass
@dataclass
class Edge(Mapping[str, float]):
    weight: float

Running this code gives:

Traceback (most recent call last):
  File "/Users/tyler.yep/Documents/Github/workshop/explore/ff.py", line 8, in <module>
    class Edge( Mapping[str, Any]):
  File "/Users/tyler.yep/Documents/Github/dataclassy/dataclassy/decorator.py", line 25, in dataclass
    return apply_metaclass(cls)
  File "/Users/tyler.yep/Documents/Github/dataclassy/dataclassy/decorator.py", line 20, in apply_metaclass
    return metaclass(to_class.__name__, to_class.__bases__, dict_, **options)
  File "/Users/tyler.yep/Documents/Github/dataclassy/dataclassy/dataclass.py", line 96, in __new__
    return type.__new__(mcs, name, bases, dict_)
TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

Thanks!

biqqles commented 3 years ago

Hmm, this is a problem caused by both typing and dataclassy using metaclasses to do their magic. The TypeError is a little cryptic, but what Python means is that both the metaclasses must be in the same inheritance chain, which obviously they are not.

>>> from typing import Mapping
>>> type(Mapping)
<class 'typing._SpecialGenericAlias'>)  # recall that the type of a data class is DataClassMeta

typing.Generic is just a regular class, so works, which is nice because generic data classes can be useful.

There are other examples of metaclasses in the standard library that would cause the same issue. For example, if you tried to create an abstract dataclass with collections.ABC:

from abc import ABC
from dataclassy import dataclass

@dataclass
class AbstractDataClass(ABC):
    pass

you would get the same error, because ABC has the metaclass ABCMeta.


Sadly, this is not easily solvable. In theory, you can merge the metaclasses by inheriting them to create a new metaclass, and then use the meta option to tell dataclassy to use that. But this is often difficult, as it seems to be here (though I tried only very briefly). If you absolutely need to subclass Mapping here, you can't use a dataclassy data class, sorry.