Open ilevkivskyi opened 6 years ago
How would one go about to fix the type signature for replace
? In TypeScript it is possible to give a replace function this type:
function replace<X extends Record<string, any>>(base: X, changes: Partial<X>): X {
return {...base, ...changes}
}
It's not a 100% correspondence, I'm using TS' Record
type instead of dataclass
and just one single changes
argument instead of a **changes
keyword argument. But in essence it's the type I want to give replace
from the dataclasses
module.
This hole is allowing errors to leak through in some of my production code. Lacking a 'typeof' operator as well, it's difficult to ensure the types are correct.
Hello and thank you for creating mypy!!
If I may, I'd like to make a case for this issue. dataclasses.replace()
is equivalent to the with
keyword in F#. For anyone wishing to do typed functional programming in Python, having replace()
checked at compile time is critical. This issue was opened 5 years ago and even the mypy documentation says:
Some functions in the dataclasses module, such as replace() and asdict(), have imprecise (too permissive) types. This will be fixed in future releases.
I can only assume that this isn't a trivial issue to fix.
It seems I haven't made a strong case for this issue after all--I've only managed to express my frustration 😢. It's just that the next time someone asks me if it's possible to do typed functional programming in Python, I'd love to say "Yes!" instead of "Almost!"
With asdict
returning an anonymous TypedDict (as planned in #8583), would this bring us any closer to closing this issue?
If the TypedDict
was not anonymous, and better yet if it was part of dataclasses (e.g. if a dataclass would have __TYPED_DICT__
and in particular __PARTIAL_TYPED_DICT__
that's total=False
) then this could perhaps be done in the type definition a'la
def replace(obj: _T, **changes: _T.__PARTIAL_TYPED_DICT__) -> _T: ...
Otherwise we could try something like #14526 where we determine the signature in a function sig hook. Curiously while in attrs.evolve
we can take the __init__
signature and mutate it to be all-ARG_NAMED_OPT
, for replace
we must make init=True
arguments ARG_NAMED
since they must be explicitly re-specified during replace
.
FWIW I remember I thought #8583 was an OK solution. But we also need to support other methods: astuple()
and replace()
. They should be quite straightforward to implement, it is just a matter of spending time on carefully writing the code.
dataclasses.replace
works properly since mypy 1.5.0. Thank you for fixing this!!
Currently, the plugin only supports dataclass creation (i.e. generation of dunder methods with correct types, including
__init__
). However, some functions in thedataclass
modules have quite broad types in typeshed, for example:so that arbitrary arguments can be give for
changes
, etc. The plugin should be able to give more precise type for these functions. In particular,asdict
could return aTypedDict
.