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 #137530DescriptionBug 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
ActivityZeroIntensity commented on Aug 8, 2025 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. sobolevn commented on Aug 8, 2025 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 JelleZijlstra commented on Aug 8, 2025 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? sobolevn commented on Aug 8, 2025 Ok, I will try this out: custom __annotate__ seems like an interesting idea. DavidCEllis commented 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. hugovk commented on Aug 12, 2025
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. hugovk commented on Oct 14, 2025 3.14.0 is out, it's time to consider this for 3.14.1. DavidCEllis commented 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. JelleZijlstra commented on Oct 20, 2025
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. DavidCEllis commented 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. added a commit that references this issue on Nov 10, 2025gh-137530: generate an __annotate__ function for dataclasses __init__ (… encukou commented on Nov 11, 2025 Reacted by Hugo van Kemenade
MetadataMetadataStatusShow more project fields
DevelopmentNo branches or pull requestsIssue actions
|