Skip to content

Add function signature hooks for dataclasses functions: replace, asdict, astuple #5152

Description

@ilevkivskyi

Currently, the plugin only supports dataclass creation (i.e. generation of dunder methods with correct types, including __init__). However, some functions in the dataclass modules have quite broad types in typeshed, for example:

def replace(obj: _T, **changes: Any) -> _T: ...

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 a TypedDict.

Activity

  1. danr commented on Mar 25, 2021

    @danr

    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.

  2. edaqa-uncountable commented on Sep 14, 2021

    @edaqa-uncountable

    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.

  3. OlegAlexander commented on Mar 3, 2023

    @OlegAlexander

    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!"

  4. ikonst commented on Mar 6, 2023

    @ikonst
    Contributor

    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.

  5. ilevkivskyi commented on Mar 7, 2023

    @ilevkivskyi
    MemberAuthor

    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.

  6. OlegAlexander commented on Sep 16, 2023

    @OlegAlexander

    dataclasses.replace works properly since mypy 1.5.0. Thank you for fixing this!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions