Skip to content
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-43901: Lazy-create an empty annotations dict in all unannotated user classes and modules #25623

Merged

Conversation

larryhastings
Copy link
Contributor

@larryhastings larryhastings commented Apr 26, 2021

The behavior of the __annotations__ member is wildly different between the three objects (function, class, module) that support it. Many of these differences are minor, but one in particular has been an awful wart for years: classes can inherit __annotations__ from their base classes. This is incontestably a misfire of the original design--it's never useful, it's only confusing, and libraries who examine annotations have had to work around it for years.

I started a thread in January 2021 in which I brought up this and other inconsistencies with respect to __annotations__:

https://mail.python.org/archives/list/[email protected]/thread/AWKVI3NRCHKPIDPCJYGVLW4HBYTEOQYL/

The reaction was positive: yes, that's a genuine problem, and there's an easy, highly compatible fix that everybody liked: classes should always have an __annotations__ dict set. So that the behavior is consistent, modules should always have one set too.

Happily, lazy-creating the annotations dict is an adequate fix, as long as the annotations dict is still stored in the object's __dict__. Code written using the old best practice, which peeks in the __dict__ for annotations on classes, will still see (or not see) the dict as it did before, so it will continue to work unchanged. (Lazy-creating the annotations dict doesn't actually fix the inherited annotations problem for such code--but this code is already working around the problem, so this is fine.) Going forward, code written for 3.10+ can always use getattr() to get the annotations dict from any object.

https://bugs.python.org/issue43901

@larryhastings
Copy link
Contributor Author

I've marked it as draft because it's not ready for merging. In particular, it needs unit tests for this new behavior. I uploaded the PR so we might get a head start on reviewing the changes.

In its current state it does pass the unit test suite.

I copied some code from module_dir into module_{get|set}_annotations().
It seemed like it had a minor flaw: if the module dict failed a
PyDict_Check(), it wanted to raise an exception.  But to raise the
exception, it wanted the name of the module, and if getting the name
failed it skipped raising the exception.  It turns out, if getting
the name of the module fails, *that* raises an exception too.  So,
rather than stomp on that exception, we should do the same thing
module_dir() does: leave that exception alone and return an error.
Added a test to ensure that the new C getsetdef for
__annotations__ doesn't interfere with classes
(or metaclasses!) redefining __annotations__
using the descriptor protocol.
@larryhastings larryhastings marked this pull request as ready for review April 27, 2021 12:37
@larryhastings
Copy link
Contributor Author

I think this is ready for review. I have test coverage for all the new lines of code. Everything seems to work. It's as good as I can make it without adult supervision.

@larryhastings larryhastings merged commit 2f2b698 into python:master Apr 30, 2021
@bedevere-bot
Copy link

@larryhastings: Please replace # with GH- in the commit message next time. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants