Incorrect format passed to __annotate__ function #125507
Copy link
Copy link
Closed as not planned
Labels
3.14bugs and security fixesbugs and security fixesstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytopic-typingtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
Description
Bug report
Consider this example:
class My:
def __annotate__(format):
assert format == 2, format
return {}
import annotationlib
annotationlib.get_annotations(My, format=2)This will fail with:
Traceback (most recent call last):
File "/Users/sobolev/Desktop/cpython2/ex.py", line 7, in <module>
annotationlib.get_annotations(My, format=2)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^
File "/Users/sobolev/Desktop/cpython2/Lib/annotationlib.py", line 688, in get_annotations
return dict(_get_dunder_annotations(obj))
~~~~~~~~~~~~~~~~~~~~~~~^^^^^
File "/Users/sobolev/Desktop/cpython2/Lib/annotationlib.py", line 817, in _get_dunder_annotations
ann = _BASE_GET_ANNOTATIONS(obj)
File "/Users/sobolev/Desktop/cpython2/ex.py", line 3, in __annotate__
assert format == 2, format
^^^^^^^^^^^
AssertionError: 1
I think that we should still pass the format in _BASE_GET_ANNOTATIONS.
I will try to fix this, since this affects my other PR: #122262
Linked PRs
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
3.14bugs and security fixesbugs and security fixesstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytopic-typingtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
Activity
sobolevn commentedon Oct 15, 2024
Ok, this is not so easy. What happens here:
__annotations__first withformat=VALUEviacpython/Lib/annotationlib.py
Line 817 in aa18fd5
NameErrorwe actually try to call__annotate__withformat=FORWARDREF:cpython/Lib/annotationlib.py
Lines 692 to 693 in aa18fd5
My code in #122262 never had any
NameErrors, so it never actually received the second callBut the question is: is this a good API? For me it feels like a very nuanced one with a lot of corner-cases.
sobolevn commentedon Oct 15, 2024
Practical question, how can I differentiate two calls in
cpython/Lib/dataclasses.py
Lines 1614 to 1630 in 3b26e36
format=VALUEthe first time, so it can be later called withformat=FORWARDREFformat=VALUEthe only time, because this is what was asked by the end userJelleZijlstra commentedon Oct 15, 2024
Would #124415 help here?
sobolevn commentedon Oct 15, 2024
@JelleZijlstra hm, I don't think so. Here's my simplified reproducer with #124415 applied:
So, looks like it does still make the first call with
format=1, even when asked to get forward refs explicitly.JelleZijlstra commentedon Oct 15, 2024
I understand the issue now. You want to return
typing.Anyin VALUE format, but in FORWARDREF format you want to avoid importingtypingand returnForwardRef("typing.Any")directly.For FORWARDREF format, we currently try using
__annotations__directly before we call__annotate__(FORWARDREF). If we change the order in which these operations happen, we could avoid your issue. I'll work on a PR for that.JelleZijlstra commentedon Oct 15, 2024
Though I'm curious if @larryhastings and @carljm have opinions here: If an object has
__annotations__that can be retrieved without error, but it also has an__annotate__that directly works with the FORWARDREF format, which should we prefer?At the sprint, we changed it so we preferred returning
__annotations__over evaluating__annotate__in the fake globals environment to implement the FORWARDREF format, which makes sense to me. But if a user-written__annotate__supports FORWARDREF directly, shouldn't we believe it?pythongh-125507: Call annotate(FORWARDREF) before trying __annotations__
carljm commentedon Oct 15, 2024
To me it seems like a useful, and sensible, invariant that if you successfully get
VALUESannotations (the default), thenFORWARDREFSannotations will be identical to that.FORWARDREFSis intended for cases whereVALUESwould fail withNameError, and the API is designed around that assumption. I would prefer not to permit objects whereFORWARDREFSannotations permanently differ fromVALUESannotations. So I would rather not make changes here.To me, the proposed approach for dataclasses seems like over-optimization. I think it's fine to always import
typing-- someone is introspecting an annotation that usestyping.Any, after all. There may be other possibilities to avoid this, but I would need to look at the problem more.larryhastings commentedon Oct 19, 2024
What Carl said. This should be documented behavior: if
__annotations__is set, that value will be returned forVALUESorFORWARDREFformats and__annotate__won't be called.JelleZijlstra commentedon Nov 20, 2024
Thanks, then let's stick with the current behavior.