You can create a AbstractDataclass class which guarantees this behaviour, and you can use this every time you have a situation like the one you described.
@dataclass
class AbstractDataclass(ABC):
def __new__(cls, *args, **kwargs):
if cls == AbstractDataclass or cls.__bases__[0] == AbstractDataclass:
raise TypeError("Cannot instantiate abstract class.")
return super().__new__(cls)
So, if Identifier inherits from AbstractDataclass instead of from ABC directly, modifying the __post_init__ will not be needed.
@dataclass
class Identifier(AbstractDataclass):
sub_tokens: List[str]
@staticmethod
def from_sub_tokens(sub_tokens):
return SimpleIdentifier(sub_tokens) if len(sub_tokens) == 1 else CompoundIdentifier(sub_tokens)
@dataclass
class SimpleIdentifier(Identifier):
pass
@dataclass
class CompoundIdentifier(Identifier):
pass
Instantiating Identifier will raise TypeError but not instantiating SimpleIdentifier or CompountIdentifier.
And the AbstractDataclass can be re-used in other parts of the code.
You can create a AbstractDataclass class which guarantees this behaviour, and you can use this every time you have a situation like the one you described.
@dataclass
class AbstractDataclass(ABC):
def __new__(cls, *args, **kwargs):
if cls == AbstractDataclass or cls.__bases__[0] == AbstractDataclass:
raise TypeError("Cannot instantiate abstract class.")
return super().__new__(cls)
So, if Identifier inherits from AbstractDataclass instead of from ABC directly, modifying the __post_init__ will not be needed.
@dataclass
class Identifier(AbstractDataclass):
sub_tokens: List[str]
@staticmethod
def from_sub_tokens(sub_tokens):
return SimpleIdentifier(sub_tokens) if len(sub_tokens) == 1 else CompoundIdentifier(sub_tokens)
@dataclass
class SimpleIdentifier(Identifier):
pass
@dataclass
class CompoundIdentifier(Identifier):
pass
Instantiating Identifier will raise TypeError but not instantiating SimpleIdentifier or CompountIdentifier.
And the AbstractDataclass can be re-used in other parts of the code.
The easiest way I have found is to check the type of the object in the __post_init__ method:
@dataclass
class Identifier(ABC):
...
def __post_init__(self):
if self.__class__ == Identifier:
raise TypeError("Cannot instantiate abstract class.")
...
Videos
Provide a canonical way to declare an abstract class variable - Ideas - Discussions on Python.org
DataClass through decorator vs base class
Is there a such thing as declaring an attribute of an abstract class in Python?
Understanding Python's new Dataclasses
This is not a protest but a question about the principles and philosophy behind the decision to use decorators in Python. Although personally I don't like decorators, I do not object to their inclusion in Python.
I am happily open to hearing arguments FOR decorators, however it seems to me that, for example, the DataClass could have been implemented as a base class and that the use of decorator is more about adding a cool new feature.
My concern is that in the pursuit of cool, Python will go down the same death spiral as the monstrosity that Java has become.
So what are the good words for decorators in Python?
I come from a C++ background, and I need to write an abstract base class, that inherits from abc. In this abstract base class, I would like to "declare" (this is C++ lingo, but I don't think variable declaration is a thing in Python) an uninstantiated variable, say `var`, which is initialized in the subclasses.
I'm wondering if there's any way to do this in Python?
The correct solution is to abandon the DataclassMixin classes and simply make the abstract classes into dataclasses, like this:
@dataclass # type: ignore[misc]
class A(ABC):
a_field: int = 1
@abstractmethod
def method(self):
pass
@dataclass # type: ignore[misc]
class B(A):
b_field: int = 2
@dataclass
class C(B):
c_field: int = 3
def method(self):
return self
The reason for the failures is that, as explained in the documentation on dataclasses, the complete set of fields in a dataclass is determined when the dataclass is compiled, not when it is inherited from. The internal code that generates the dataclass's __init__ function can only examine the MRO of the dataclass as it is declared on its own, not when mixed in to another class.
It's necessary to add # type: ignore[misc] to each abstract dataclass's @dataclass line, not because the solution is wrong but because mypy is wrong. It is mypy, not Python, that requires dataclasses to be concrete. As explained by ilevkivskyi in mypy issue 5374, the problem is that mypy wants a dataclass to be a Type object and for every Type object to be capable of being instantiated. This is a known problem and awaits a resolution.
The behavior in the question and in the solution is exactly how dataclasses should behave. And, happily, abstract dataclasses that inherit this way (the ordinary way) can be mixed into other classes willy-nilly no differently than other mix-ins.
Putting the mixin as the last base class works without error:
@dataclass
class ADataclassMixin:
a_field: int = 1
class A(ABC, ADataclassMixin):
@abstractmethod
def method(self):
pass
@dataclass
class BDataclassMixin:
b_field: int = 2
class B(A, BDataclassMixin):
def method(self):
return self
o = B(a_field=5)
print((o.a_field, o.b_field)) # (5,2)