Hi everyone!
We’re implementing dataclass_transform
support in PyCharm and I came across one puzzling bit in the spec that mypy and pyright seem to interpreter differently. Sorry if it was brought up already, I haven’t found a relevant discussion. Namely, it’s the behavior regarding absence of an explicit kw_only
argument in a field specifier call. The specification states:
If true, the field will be keyword-only. If false, it will not be keyword-only. If unspecified, the value of the
kw_only
parameter on the object decorated withdataclass_transform
will be used, or if that is unspecified, the value ofkw_only_default
ondataclass_transform
will be used.
So it seems that the order of precedence for treating a field as a keyword-only parameter of a dataclass constructor is the following (from the most priority to the least):
- Explicit
kw_only
keyword argument in the corresponding field specifier call kw_only
argument in the decorator application or the dataclass inheritance listkw_only_default
keyword argument in the application of@dataclass_transform
itself
However, where does the default value of the optional kw_only
parameter of the field specifier fall here? Should it be taken into account at all?
For instance, take the following example:
from typing import Callable, Type, TypeVar, dataclass_transform
def my_field(kw_only=False):
...
@dataclass_transform(field_specifiers=(my_field,))
def my_dataclass(**kwargs) -> Callable[[type], type]:
...
@my_dataclass(kw_only=True)
class Order:
id: str = my_field()
addr: list[str]
Order() # pyright: (id: str, *, addr: list[str]), mypy: (*, id: str, addr: list[str])
Here, pyright takes the default value of the my_field
’s kw_only
default making id
an ordinary parameter of the constructor despite the fact that @my_dataclass
has kw_only=True
. At the same time, Mypy seems to ignore the default value in the field specifier, considering id
a keyword-only parameter like the subsequent addr
.
What would be the right policy here? In particular, how “if unspecified” should be treated: as an absence of a keyword argument in a call or as an absence of the parameter in a field specifier definition?
It also raises a question how type checkers should treat a mistmatch between values of *_default
arguments in dataclass_transform
application and default values of the corresponding parameter defaults of a decorated function, or __init_subclass__
or __new__
of a decorated class.
For instance here:
from typing import Callable, dataclass_transform, reveal_type
def my_field():
...
@dataclass_transform(kw_only_default=False, field_specifiers=(my_field,))
def my_dataclass(kw_only=True, **kwargs) -> Callable[[type], type]:
...
@my_dataclass()
class Order:
id: str
addr: list[str]
Order() # pyright: (id: str, add: list[str]), mypy: (id: str, add: list[str])
It seems that both pyright and mypy ignore the default value of kw_only
here according to the spec, but it might be confusing for someone reading the declaration of my_dataclass
. I guess the answer will be that type checkers can report this if they deem it necessary, just want to check if there was some subtle reason for not doing that.