You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Can't assign entry to __annotations__ unless a class has existing annotation #95532
👋 @ericvsmith we have a schema definition framework built atop pydantic, and directly adding items to __annotations__ makes it possible to easily "copy" fields between schema classes, we utilise this a lot. This use case works fine for us, unless the schema class doesn't have any existing fields (as illustrated in this issue). I'm happy to describe this in more details if necessary.
I'm not sure we really want to support adding items to __annotations__ directly.
I assume you're saying you like the 3.10/3.11 behavior (which doesn't fail), and want it backported to 3.9? 3.9 is only getting security fixes now.
@ericvsmith I think the 3.10/11 is an improvement from 3.9, but the bug here is that you can't assign item to __annotations__unless there's already existing annotation in the class, which is inconsistent.
So from the example above:
>>>classFoo:
... __annotations__["b"] =int
...
>>>Foo.__annotations__
{}
>>># BUG: assignment did not work ^ where is the `b` field?>>>classFoo:
... a: float
... __annotations__["b"] =int
...
>>>Foo.__annotations__
{'a': <class'float'>, 'b': <class'int'>}
>>># NOTE: now the assignment worked for both `a` and `b`, only because there was existing `a` field
Ah, I understand now where does the b field go in this scenario:
>>>classFoo:
... __annotations__["b"] =int
...
>>>Foo.__annotations__
{}
>>># BUG: where is `b`?
See:
Python3.12.0a0 (heads/fix-issue-95532-dirty:698fa8bf60, Aug22022, 09:34:18) [Clang12.0.0 (clang-1200.0.32.2)] ondarwinType"help", "copyright", "credits"or"license"formoreinformation.
>>>__annotations__
{}
>>># module __annotations__ is empty - so far good>>>classFoo:
... __annotations__["c"] =float
...
>>>Foo.__annotations__
{}
>>>__annotations__
{'c': <class'float'>}
>>># `c` went into the module __annotations__, but see what happens next:>>>classBar:
... a: int
... __annotations__["b"] =float
...
>>>__annotations__
{'c': <class'float'>}
>>>Bar.__annotations__
{'a': <class'int'>, 'b': <class'float'>}
>>># both `a` and `b` are now in the the Bar class __annotations__
So I would say in the Foo example, __annotations__ should point to Foo.__annotations__ not the module's __annotations__.
And this issue is reproducible with the 3.6 PEP-0526 implementation #72172. Do we agree that __annotations__ in the Foo example should bind to Foo.__annotations__? Would be happy to try to PR a fix for that.
This strikes me as a (supprising) bug. Any assignments in class scope scope to a writable name should be in the class namespace unless a global declaration says otherwise.
When you find the code that does this, ping the author (found using git blame or git log) either here or on a PR.
Note that 10 SETUP_ANNOTATIONS is not present.
Why? Because find_ann function is looking for AnnAssign_kind statement kind to actually set annotations up.
The simpliest option I see is to always call SETUP_ANNOTATIONS for class bodies, similar to how we treat __qualname__, __name__, and __module__.
The downside of this solution is that we slow down all classes even without any annotations. How bad is it? I don't know: I will need to write a benchmark.
Yeah, IIRC we already understood there may be downsides when we decided to conditionally create __annotations__. It's totally OK to always create __annotations__ if it turns out having it defined is more important than the (modest) memory savings. (Also having class scope one bound to global one is definitely a bug, probably something is missing in symbol table analysis).
Also having class scope one bound to global one is definitely a bug, probably something is missing in symbol table analysis
@ilevkivskyi to clarify, are you talking about __annotations__ being resolved to a global variable inside a class scope (when __annotations__ is not defined), like:
@sobolevn understood, was just pointed to the example @ilevkivskyi was referring to in the comments above. Sounds like @ilevkivskyi is ok with just creating __annotations__ at all times, which will resolve both problems highlighted in this issue. In that case unless there's any concerns will go ahead and try to PR that(?)
I think I misunderstood, I thought there was some case where it was bound to global one while class-level one is also defined. But it looks like the class-level one is generated lazily when accessed. IIUC this was added later (found it in #25623), while in 3.6 accessing __annotations__ on a class without annotations raised attribute error. So one would have:
This was consistent with how any other name (like a above) would behave in this case. But with lazy generation of __annotations__ this now becomes confusing.
Reacted by sobolevn, Rafal Wojdyla and Martin DeMello
Activity
ericvsmith commentedon Aug 1, 2022
What's the use case for this? I'm not sure we really want to support adding items to
__annotations__directly.ravwojdyla commentedon Aug 1, 2022
👋 @ericvsmith we have a schema definition framework built atop pydantic, and directly adding items to
__annotations__makes it possible to easily "copy" fields between schema classes, we utilise this a lot. This use case works fine for us, unless the schema class doesn't have any existing fields (as illustrated in this issue). I'm happy to describe this in more details if necessary.From PEP-0526:
ericvsmith commentedon Aug 1, 2022
Thanks, @ravwojdyla
Man, the formatting of that PEP section is messed up!
I assume you're saying you like the 3.10/3.11 behavior (which doesn't fail), and want it backported to 3.9? 3.9 is only getting security fixes now.
ravwojdyla commentedon Aug 1, 2022
@ericvsmith I think the 3.10/11 is an improvement from 3.9, but the bug here is that you can't assign item to
__annotations__unless there's already existing annotation in the class, which is inconsistent.So from the example above:
Does it make sense?
ericvsmith commentedon Aug 1, 2022
Ah. I missed that the returned dict was empty in the first 3.10 example.
@larryhastings thoughts?
ravwojdyla commentedon Aug 2, 2022
Ah, I understand now where does the
bfield go in this scenario:See:
So I would say in the
Fooexample,__annotations__should point toFoo.__annotations__not the module's__annotations__.Also note that this works as expected:
So it seems like the existence of the
aannotated field in theBazclass is somehow binding__annotations__to the class.ravwojdyla commentedon Aug 2, 2022
And this issue is reproducible with the 3.6 PEP-0526 implementation #72172. Do we agree that
__annotations__in theFooexample should bind toFoo.__annotations__? Would be happy to try to PR a fix for that.ravwojdyla commentedon Aug 8, 2022
👋 @ericvsmith what's the best way to get feedback/guidance for this issue and question above?
ericvsmith commentedon Aug 9, 2022
@ravwojdyla : Maybe mention it on discuss.python.org, pointing to this issue.
ravwojdyla commentedon Aug 11, 2022
Posted here: https://discuss.python.org/t/request-for-guidance-on-gh-95532/18124
terryjreedy commentedon Aug 13, 2022
This strikes me as a (supprising) bug. Any assignments in class scope scope to a writable name should be in the class namespace unless a global declaration says otherwise.
When you find the code that does this, ping the author (found using git blame or git log) either here or on a PR.
sobolevn commentedon Sep 5, 2022
Very interesting! Let's dive into it.
Let's see why they are different:
Note
10 SETUP_ANNOTATIONSNote that
10 SETUP_ANNOTATIONSis not present.Why? Because
find_annfunction is looking forAnnAssign_kindstatement kind to actually set annotations up.The simpliest option I see is to always call
SETUP_ANNOTATIONSfor class bodies, similar to how we treat__qualname__,__name__, and__module__.The downside of this solution is that we slow down all classes even without any annotations. How bad is it? I don't know: I will need to write a benchmark.
Other ideas?
ravwojdyla commentedon Sep 6, 2022
Nice @sobolevn!
Btw https://docs.python.org/3/library/dis.html#opcode-SETUP_ANNOTATIONS
Regarding Terry request:
@ilevkivskyi FYI ^
ilevkivskyi commentedon Sep 6, 2022
Yeah, IIRC we already understood there may be downsides when we decided to conditionally create
__annotations__. It's totally OK to always create__annotations__if it turns out having it defined is more important than the (modest) memory savings. (Also having class scope one bound to global one is definitely a bug, probably something is missing in symbol table analysis).sobolevn commentedon Sep 6, 2022
@ilevkivskyi to clarify, are you talking about
__annotations__being resolved to a global variable inside a class scope (when__annotations__is not defined), like:?
ravwojdyla commentedon Sep 6, 2022
@sobolevn I believe @ilevkivskyi is talking about this case:
See above: #95532 (comment), this type of assignment to
__annotations__works if__annotations__already exists at the class scope.sobolevn commentedon Sep 6, 2022
@ravwojdyla this is exactly the case I've showed in #95532 (comment) using
a = {}as more general case.ravwojdyla commentedon Sep 6, 2022
@sobolevn understood, was just pointed to the example @ilevkivskyi was referring to in the comments above. Sounds like @ilevkivskyi is ok with just creating
__annotations__at all times, which will resolve both problems highlighted in this issue. In that case unless there's any concerns will go ahead and try to PR that(?)sobolevn commentedon Sep 6, 2022
I think it is better to let a person to speak for themself :)
Let's wait for some more information before diving into it 😉
ilevkivskyi commentedon Sep 6, 2022
I think I misunderstood, I thought there was some case where it was bound to global one while class-level one is also defined. But it looks like the class-level one is generated lazily when accessed. IIUC this was added later (found it in #25623), while in 3.6 accessing
__annotations__on a class without annotations raised attribute error. So one would have:This was consistent with how any other name (like
aabove) would behave in this case. But with lazy generation of__annotations__this now becomes confusing.ravwojdyla commentedon Sep 6, 2022
Also FYI #88067 (comment) and #88067 (comment)