Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork33.4k
gh-137530: generate an __annotate__ function for dataclasses __init__#137711
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Conversation
needed for _add_slots
Lib/dataclasses.py Outdated
| elifisinstance(v,annotationlib.ForwardRef): | ||
| string_annos[k]=v.evaluate(format=annotationlib.Format.STRING) | ||
| else: | ||
| string_annos[k]=annotationlib.type_repr(v) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
This is a bit unfortunate because we could get better STRING annotations by calling with the STRING format on the original class (and its base classes).
An approach to do this would be to make__init__.__annotate__ delegate to callingget_annotations() on the class itself, and on any base classes that are contributing fields, with the appropriate format, then processing the result to add"return": None and filter out any fields that don't correspond to__init__ parameters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I did think about that, but wasn't certain about the extra complexity, but I've since noticed that if there's a hint thatcontains a ForwardRef (likelist[undefined]) the result leaks all the ForwardRef details.
I've added a test for that example to demonstrate why this is necessary.
I've taken a slightly different approach, in that I'm gathering all inherited annotations and using them to update the values from the fields. The original source annotation logic this replaces wasn't__init__ specific so I've tried to keep the__annotate__ generator non-specific too.
There are additional commits as this change meant that__annotate__ now (always) has a reference to the original class which broke#135228 - this now also includes an additional test and fix for the issue I brought up there as I already needed to update the fields for this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I also unintentionally discovered that the__annotate__ functions CPython generates for methods have an incorrect__qualname__ while fixing the__qualname__ for the function generated here.
The possibility to replace only occurs in _add_slots.
DavidCEllis commentedAug 15, 2025
On doing some other work, I think the logic for Problem for both: fromannotationlibimportget_annotationsfromdataclassesimportdataclass@dataclassclassExample:a:list[defined_late]defined_late=intprint(get_annotations(Example.__init__)) For |
DavidCEllis commentedAug 15, 2025
Ah, while this is resolvable for I'm leaning towards making |
Also split out the single large test into multiple smaller tests
DavidCEllis commentedAug 15, 2025 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Thoughts:
Edit: I realised changing the signature would make |
Use `__class__` as the first argument name so `_update_func_cell_for__class__` can update it.
DavidCEllis commentedAug 15, 2025
Ok, I'm going to try to leave this alone for some review. The only functional1 thing Iknow I'm not completely happy with is this example from the tests2: fromdataclassesimportdataclass,fieldfromannotationlibimportget_annotations@dataclassclassF:not_in_init:list[undefined]=field(init=False,default=None)in_init:intannos=annotationlib.get_annotations(F.__init__)# NameError I don't have a good approach for this. I don't think you can use Footnotes |
DavidCEllis commentedAug 25, 2025
The ideal solution I would like for this would require an additional feature from Then a solution more like the original version of this PR could work. |
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
JelleZijlstra left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Looks good, except for the small suggestions above.
JelleZijlstra commentedOct 21, 2025
You'll also need a NEWS entry. Since we're planning to backport to 3.14 to fix the release blocker, I think it's also worth mentioning in What's New as this will be a fairly big change in 3.14.1. |
encukou commentedNov 3, 2025
Looks like the What's New entry all that's left here.@DavidCEllis, do you want to write one? Or I can do it to help get this in. |
DavidCEllis commentedNov 3, 2025
I'm happy for you to do it. It's not something I've needed to do before so I don't actually know where it would need to go. |
encukou commentedNov 3, 2025
It would go here. For the backport, there'd be a “Notable changes” section at the end, likefor 3.13. @JelleZijlstra ... um, are you sure this is worth it? From anoutside point of view this looks like a straightforward regression fix. |
encukou commentedNov 7, 2025
ping@JelleZijlstra I plan to merge Monday or so if I don't hear back, to give enough time for a backport. |
12837c6 intopython:mainUh oh!
There was an error while loading.Please reload this page.
Thanks@DavidCEllis for the PR, and@encukou for merging it 🌮🎉.. I'm working now to backport this PR to: 3.14. |
Sorry,@DavidCEllis and@encukou, I could not cleanly backport this to |
…sses __init__ (pythonGH-137711)(cherry picked from commit12837c6)Co-authored-by: David Ellis <ducksual@gmail.com>
GH-141352 is a backport of this pull request to the3.14 branch. |
JelleZijlstra commentedNov 10, 2025
Sorry for not responding here earlier, haven't been paying enough attention to GitHub.
Fair enough, my thinking was that this significantly changes the annotations people will see if they introspect dataclasses init functions. But our What's New documents don't seem to even have sections for changes in bugfix releases recently, so maybe it's not worth it. |
encukou commentedNov 11, 2025
They do, at the end -- for example,https://docs.python.org/3.12/whatsnew/3.12.html#notable-changes-in-3-12-4 |
Uh oh!
There was an error while loading.Please reload this page.
This is one approach to resolving#137530
It generates a new
__annotate__function to attach to the__init__method and removes the in-line annotations that are now unused.It's possible to keep the original source annotations if necessary, but they provide a second possibly incorrect source of the information and they will probably also cause additional issues with#135228 as they may keep the original class around when ForwardRef values are generated.
The generated
__annotate__will also keep these references and need to be regenerated. I can extend this or follow up to handle these references but I didn't want to do too much in one PR.