GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Calling help() on a dataclass shows dataclass internal names instead of type names in 3.14 or later · Issue #137530 · python/cpython

Calling help() on a dataclass shows dataclass internal names instead of type names in 3.14 or later #137530

New issue
Copy link
New issue
Copy link
Closed
Closed
Calling help() on a dataclass shows dataclass internal names instead of type names in 3.14 or later#137530
Copy link
Assignees
sobolevn
Labels
3.14bugs and security fixesbugs and security fixes3.15bugs and security fixesbugs and security fixesdeferred-blockerstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytopic-dataclassestype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

Description

@DavidCEllis
DavidCEllis
opened on Aug 7, 2025
Issue body actions

Bug report

Bug description:

If you call help on a dataclass in Python 3.14 or later it shows the internal type names used for the exec call in the signature.

Docstring included to hide the dataclasses generated __doc__ which would just confuse things.

from dataclasses import dataclass

@dataclass
class Example:
    "docstring"
    x: int

help(Example)

Before, on 3.13

Help on class Example in module __main__:

class Example(builtins.object)
 |  Example(x: int) -> None
 |
 |  docstring
 |
 |  Methods defined here:
 |
 |  __eq__(self, other)
 |      Return self==value.
 |
 |  __init__(self, x: int) -> None
 |      Initialize self.  See help(type(self)) for accurate signature.
...

Now, on 3.14 or later

Help on class Example in module __main__:

class Example(builtins.object)
 |  Example(x: __dataclass_type_x__) -> __dataclass___init___return_type__
 |
 |  docstring
 |
 |  Methods defined here:
 |
 |  __eq__(self, other)
 |      Return self==value.
 |
 |  __init__(self, x: __dataclass_type_x__) -> __dataclass___init___return_type__
 |      Initialize self.  See help(type(self)) for accurate signature.
...

This looks a bit messy and reveals inner details that users of help probably don't need to see.

It looks like this comes from inspect.signature(cls, annotation_format=Format.STRING) being used in pydoc, but I think it's a dataclasses issue.

attrs doesn't suffer the same fate as it attaches annotations as __annotations__ instead of defining them in the source code string to be evaluated.

Inspired by that, for my own dataclass-like implementation for 3.14+ I'm collecting the annotations and generating an __annotate__ function which appears to solve this issue: see here

dataclasses could potentially do something similar.

CPython versions tested on:

3.14, CPython main branch

Operating systems tested on:

No response

Linked PRs

  • gh-137530: generate an __annotate__ function for dataclasses __init__ #137711
  • [3.14] gh-137530: generate an __annotate__ function for dataclasses __init__ (GH-137711) #141352

Activity

  1. DavidCEllis
    added
    type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
    on Aug 7, 2025
  2. StanFromIreland
    added
    stdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directory
    topic-dataclasses
    on Aug 7, 2025
  3. ZeroIntensity
    added
    release-blocker
    on Aug 8, 2025
  4. ezio-melotti
    added this to Release and Deferred blockers 🚫on Aug 8, 2025
  5. github-project-automation
    moved this to Todo in Release and Deferred blockers 🚫on Aug 8, 2025
  6. ZeroIntensity commented on Aug 8, 2025

    @ZeroIntensity
    ZeroIntensity
    on Aug 8, 2025
    Member
    More actions

    This is a 3.14 regression, so I've marked it as a blocker for now. This doesn't look like something that can easily be fixed, but we should come to a decision before 3.14.0.

    Reacted by Adam Turner
  7. sobolevn commented on Aug 8, 2025

    @sobolevn
    sobolevn
    on Aug 8, 2025
    Member
    More actions

    Idea: we can at least define a table with translation. Something like:

    class Example(builtins.object)
     |  Example(x: __dataclass_type_x__) -> __dataclass___init___return_type__
     |
     |  docstring
     |
     |  Internal magic names:
     |     __dataclass___init___return_type__ -> None
     |     __dataclass_type_x__ -> int
     |
     |  Methods defined here:
     |
     |  __eq__(self, other)
     |      Return self==value.
     |
     |  __init__(self, x: __dataclass_type_x__) -> __dataclass___init___return_type__
     |      Initialize self.  See help(type(self)) for accurate signature.

    What do you think?

    CC @JelleZijlstra

  8. JelleZijlstra commented on Aug 8, 2025

    @JelleZijlstra
    JelleZijlstra
    on Aug 8, 2025
    Member
    More actions

    That's unfortunate, thanks for noticing!

    I think we should fix this but it doesn't seem critical for 3.14.0 since it's not runtime behavior; it can wait for 3.14.1.

    I don't think @sobolevn's translation table is a great solution; ideally the internal names shouldn't bleed to users at all. Maybe we can attach a custom __annotate__ function that outputs nicer STRING format outputs?

  9. sobolevn
    self-assigned this
    on Aug 8, 2025
  10. sobolevn commented on Aug 8, 2025

    @sobolevn
    sobolevn
    on Aug 8, 2025
    Member
    More actions

    Ok, I will try this out: custom __annotate__ seems like an interesting idea.

  11. DavidCEllis commented on Aug 8, 2025

    @DavidCEllis
    DavidCEllis
    on Aug 8, 2025
    ContributorAuthor
    More actions

    The issue is caused by get_annotations with Format.STRING attempting to get the text from the source code and in this case succeeding, but the output is not what you actually want users to see.

    These values are specifically from the generated __init__ function.

    It may be worth noting that if you have a forward reference in your dataclass annotations, this will also appear in __annotations__ (and get_annotations with format.VALUE).

    from dataclasses import dataclass
    from annotationlib import get_annotations, Format
    
    @dataclass
    class Example:
        x: undefined
    
    print(get_annotations(Example.__init__, format=Format.STRING))
    # {'x': '__dataclass_type_x__', 'return': '__dataclass___init___return_type__'}
    
    print(Example.__init__.__annotations__)
    # {'x': ForwardRef('undefined', is_class=True, owner=<class '__main__.Example'>), 'return': None}

    Maybe we can attach a custom annotate function that outputs nicer STRING format outputs?

    As linked earlier, this is what I currently have for my own implementation in ducktools-classbuilder

    from annotationlib import Format, ForwardRef, type_repr
    
    def make_annotate_function(annos):
        # Construct an annotation function from __annotations__
        def annotate_func(format):
            match format:
                case Format.VALUE | Format.FORWARDREF:
                    return {
                        k: v.evaluate(format=format)
                        if isinstance(v, ForwardRef) else v
                        for k, v in annos.items()
                    }
                case Format.STRING:
                    string_annos = {}
                    for k, v in annos.items():
                        if isinstance(v, str):
                            string_annos[k] = v
                        elif isinstance(v, ForwardRef):
                            string_annos[k] = v.evaluate(format=Format.STRING)
                        else:
                            string_annos[k] = type_repr(v)
                    return string_annos
                case _:
                    raise NotImplementedError(format)
        return annotate_func

    Where annos would be {f.name: f.type for f in fields} with the resulting function attached to the __init__ method.

    Edit: Along with "return": None included in the annotation dict
    Edit 2: And possibly with some extra filtering for f.init.

  12. picnixz
    added
    3.14bugs and security fixesbugs and security fixes
    3.15bugs and security fixesbugs and security fixes
    on Aug 8, 2025
  13. hugovk commented on Aug 12, 2025

    @hugovk
    hugovk
    on Aug 12, 2025
    Member
    More actions

    I think we should fix this but it doesn't seem critical for 3.14.0 since it's not runtime behavior; it can wait for 3.14.1.

    Agreed, let's wait for 3.14.1 and move to deferred-blocker.

  14. hugovk
    added
    deferred-blocker
    and removed
    release-blocker
    on Aug 12, 2025
  15. hugovk commented on Oct 14, 2025

    @hugovk
    hugovk
    on Oct 14, 2025
    Member
    More actions

    3.14.0 is out, it's time to consider this for 3.14.1.

  16. DavidCEllis commented on Oct 14, 2025

    @DavidCEllis
    DavidCEllis
    on Oct 14, 2025
    ContributorAuthor
    More actions

    I've kind of been sitting on this PR: #137711

    Other than probably needing some clean up/review, the problem I have with the state of that PR is VALUE annotations remain somewhat broken. An init=False field with a forward reference will break them even though that field isn't necessary for the __init__ annotations.

    Unless dataclasses is going to start messing with annotationlib internals I think you either have to live with that or allow ForwardRef values to potentially leak into the VALUE annotations by using those as the source instead.

    There is the possibility of just generating string annotations and attaching them to __annotations__ as if this were generated under __future__ annotations. Those would at least be consistent even if it would also defeat the point of the new deferred annotations.

    I proposed this idea on discuss which would provide the tools needed to solve this cleanly (and for third party dataclass-like implementations) but there seemed to be absolutely no interest. Even so I imagine something like that might only be usable in a future release so we may need either a compromise or some internal work for 3.14.

  17. JelleZijlstra commented on Oct 20, 2025

    @JelleZijlstra
    JelleZijlstra
    on Oct 20, 2025
    Member
    More actions

    An init=False field with a forward reference will break them

    That doesn't seem like the worst thing to me; it's generally true in 3.14 that VALUE format annotations are sometimes not available, and you have to use FORWARDREF/STRING in those cases. Not ideal that VALUE here doesn't work when in theory it could work, but I think your PR is a great improvement already.

  18. DavidCEllis commented on Oct 21, 2025

    @DavidCEllis
    DavidCEllis
    on Oct 21, 2025
    ContributorAuthor
    More actions

    It's an improvement for STRING and FORWARDREF annotations. Technically VALUE can now fail in places it would have "succeeded" before (by leaking forward references).

    I don't like that it has to replicate the logic of the field gathering in order to do this. This isn't too bad for dataclasses, but other implementations will have extra things (such as attrs converters) which need to be taken into account when generating annotations that are going to make their annotate functions more complicated.

  19. encukou
    added a commit that references this issue on Nov 10, 2025

    gh-137530: generate an __annotate__ function for dataclasses __init__ (…

    12837c6
  20. JelleZijlstra
    added 2 commits that reference this issue on Nov 10, 2025

    [3.14] pythongh-137530: generate an __annotate__ function for datacla…

    9c87484

    [3.14] gh-137530: generate an __annotate__ function for dataclasses _…

    727cdcb
  21. encukou commented on Nov 11, 2025

    @encukou
    encukou
    on Nov 11, 2025
    Member
    More actions

    Thank you @DavidCEllis for the report and fix, and @JelleZijlstra for reviews!

    Reacted by Hugo van Kemenade
  22. encukou
    closed this as completedon Nov 11, 2025
  23. github-project-automation
    moved this from Todo to Done in Release and Deferred blockers 🚫on Nov 11, 2025
  24. StanFromIreland
    added a commit that references this issue on Dec 6, 2025

    pythongh-137530: generate an __annotate__ function for dataclasses __…

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

Metadata

Metadata

Assignees

  • @sobolevn
    sobolevn

Labels

3.14bugs and security fixesbugs and security fixes3.15bugs and security fixesbugs and security fixesdeferred-blockerstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytopic-dataclassestype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

Projects

  • Release and Deferred blockers 🚫
    Status
    Done
    Show more project fields

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.