I have the following code:
from dataclasses import dataclass
class Expr:
def __repr__(self):
fields = ', '.join(f'{v!r}' for v in self.__dict__.values())
return f'{self.__class__.__name__}({fields})'
@dataclass(repr=False)
class Literal(Expr):
value: object
@dataclass(repr=False)
class Binary(Expr):
lhs: Expr
op: str
rhs: Expr
This code allows me to define expression trees and to match against them, e.g.:
def fmt_ast(expr: Expr) -> str:
match expr:
case Literal(x):
return str(x)
case Binary(lhs, op, rhs):
return f'({op} {fmt_ast(lhs)} {fmt_ast(rhs)})'
assert False, 'Unreachable'
expr = Binary(Literal(42), '+', Literal(23))
print(fmt_ast(expr))
# (+ 42 23)
However, I would like to avoid having to decorate each Expr subclass manually. I.e. I want to skip writing @dataclass(repr=False). Based on another answer, I rewrote Expr as follows:
class Expr:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
dataclass(repr=False)(cls)
def __repr__(self):
fields = ', '.join(f'{v!r}' for v in self.__dict__.values())
return f'{self.__class__.__name__}({fields})'
… and this seems to work (even after removing the decorators from the subclasses). The thing is, I don’t understand why it works: sure, I’m invoking the dataclass decorator but I’m not doing anything with the result, i.e.with the decorated type. I don’t assign it to anything (to what?!) and I don’t return it, since __init_subclass__ has no meaningful return value. And yet, the code continues to run as if the subclasses had been decorated with dataclass.
However, mypy (0.942) calls my bluff:
test/__init__.py:29: error: Class "test.Literal" doesn't define "__match_args__" test/__init__.py:31: error: Class "test.Binary" doesn't define "__match_args__" test/__init__.py:36: error: Too many arguments for "Binary" test/__init__.py:36: error: Too many arguments for "Literal"
I believe that mypy is correct, which leaves me with two questions:
- Why does this seem to work at all?
- How to do this properly, i.e. in a way that satisfies
mypy, without reimplementing thedataclassfunctionality from scratch insideExpror its metaclass?