classification
Title: Lazy-create an empty annotations dict in all unannotated user classes and modules
Type: enhancement Stage: resolved
Components: Interpreter Core Versions: Python 3.10
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: larry Nosy List: Guido.van.Rossum, Jelle Zijlstra, eric.smith, erlendaasland, gvanrossum, kokxxxxik, larry, pablogsal
Priority: release blocker Keywords: patch

Created on 2021-04-21 06:44 by larry, last changed 2021-04-30 23:27 by erlendaasland. This issue is now closed.

Pull Requests
URL Status Linked Edit
PR 25623 merged larry, 2021-04-26 05:55
PR 25752 closed larry, 2021-04-30 15:57
PR 25754 merged pablogsal, 2021-04-30 15:59
Messages (39)
msg391490 - (view) Author: Larry Hastings (larry) * (Python committer) Date: 2021-04-21 06:44
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 is never useful, it is 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/python-dev@python.org/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.

This won't be a large change.  However, there are a lot of changes flying around with respect to __annotations__ right now.  My plan is to get the work ready, then when the dust is mostly-settled I'll get it updated, reviewed, and merged.  Definitely before Python 3.10b1.

Long term, I'd like to get the other changes to annotations I proposed in that thread:
  * del o.__annotations__ always raises an exception.
  * o.__annotations__ is permitted to be None.
  * You can only set o.__annotations__ to either a dict or None.
But it's probably best to not change everything all at once.
msg391491 - (view) Author: Larry Hastings (larry) * (Python committer) Date: 2021-04-21 06:53
Huh.  The sample code in my thread got goofed up somewhere along the way--maybe it's my fault, maybe it was done to me by some automated process.  Anyway, the example demonstrating classes inheriting annotations was meant to be formatted like this:

    class A:
        ax:int=3
    class B(A):
        pass
    print(getattr(B, '__annotations__', {}))

Maybe some over-smart doodad decided that no paragraph would ever start with spaces, so they helpfully removed them?  Hmm.
msg391495 - (view) Author: Larry Hastings (larry) * (Python committer) Date: 2021-04-21 07:26
Preliminary patch is 17 lines.  Ah well.  I must be terrible at this!
msg391542 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2021-04-21 20:26
Sounds like a plan, I agree that the original design misfired here (we were probably worried about million-line code bases with tens of thousands of classes having to pay the price of tens of thousands of empty dicts, but I now think that was an unnecessary worry -- that should be a few megabytes on a very much larger total).

But given that you're not done yet:

- Is it possible to create __annotations__ lazily? (IIRC in January we came to a conclusion about this, something like yes for modules but for classes, or the other way around?)

- Why would __annotations__ ever be None? Why do you allow setting it to None? Do we know of people ever write '__annotations__ = None' in their class or write 'cls.__annotations__ = None'?

- And where's your PR?
msg391555 - (view) Author: Larry Hastings (larry) * (Python committer) Date: 2021-04-21 22:56
> - Is it possible to create __annotations__ lazily? (IIRC in January we came to a conclusion about this, something like yes for modules but for classes, or the other way around?)

Maybe for modules, definitely not for classes.  The problem is that best practice for classes is to look in the class dict for __annotations__.  That sidesteps the inheritance problem.

Functions already lazily create a dict __annotations__ if not set.  And it's not stored in the function dict.  So nobody ever looks in the function dict for __annotations__.  That all works fine.

Would you prefer that *just classes* lazily create __annotations__?  It's not hard code to write, but I was being cautious.  "always set __annotations__ to an empty dict" is the easiest code to write and get correct.  And since modules are not all that numerous even in the largest projects, it seemed like this un-optimized approach would have a minimal contribution to the heat death of the universe.

It's also remotely possible that someone out there does look in the module dict for __annotations__, though I admit I've never seen it.  It seems like it'd be tempting to write your code that way though:

    if isinstance(o, (type, types.ModuleType)):
        ann = o.__dict__.get("__annotations__", None)
    elif callable(o):
        ann = o.__annotations__
    else:
        raise ValueError(f"{o!r} doesn't support annotations")


> - Why would __annotations__ ever be None?

Simply because it's the cheapest sensible way to indicate "this object has no annotations".  It would be nice if the world accepted __annotations__ being None to mean "no annotations".  But I don't think we're there.

I note that the function object has a special setter for __annotations__ (func_set_annotations()), since at least Python 3.1, which explicitly only allows setting __annotations__ to either a dict or None.  (I didn't have 3.0 handy.)


> Why do you allow setting it to None?

Me?  I've never contributed code to Python that restricts the permissible types of values one is allowed to set on __annotations__.

Function objects are opinionated as mentioned (either dict or None), classes and modules have no opinion whatsoever, and allow you to set __annotations__ to any value.  That was all true long before I started working on annotations.


> Do we know of people ever write '__annotations__ = None' in their class or write 'cls.__annotations__ = None'?

AFAIK I've never seen anyone set __annotations__ to None.


> - And where's your PR?

Not done yet.  I only started it last night, and there were a lot of test failures--it turns out, a *lot* of regression tests do a sanity check that __annotations__ isn't set on classes (and maybe modules too).  For example, I was surprised that test_opcodes has two such test failures.
msg391556 - (view) Author: Larry Hastings (larry) * (Python committer) Date: 2021-04-21 22:59
By the way, here's a tidbit I never got around to posting in c.l.p-d.

I noted in the conversation in January that attrs is an outlier here: it *doesn't* look in the class dict for __annotations__.  Instead, it has some complicated code where it asks the class for its annotations, then iterates over the __mro__ and asks every base class for *its* annotations.  If the class and a base class have the same class dict (using the "is" operator iirc) then attrs says "oh, that class doesn't have its own annotations, it's inheriting them" and reacts appropriately.

I emailed Hynek to ask him why he did it that way.  It turns out, he did that because at the time he didn't know you could peek in the class dict for __annotations__.  He literally had a todo item saying "change to looking in the class dict".
msg391558 - (view) Author: Larry Hastings (larry) * (Python committer) Date: 2021-04-21 23:14
It occurs to me that part of this work should also be a new "best practices for __annotations__" entry in the Python docs.


Best practices for working with annotations, for code that requires a minimum Python version of 3.10+:

Best practice is to call either inspect.get_annotations() or typing.get_type_hints() to access annotations.  But if you want to access '__annotations__' on an object directly:

* Always access the annotations on an object using the attribute interface, either o.__annotations__ or getattr(o, '__annotations__').  Accessing '__annotations__' through other mechanisms (e.g. looking in the class dict) is unsupported.
* Assume that o.__annotations__ is always either a dict or None.
* It's best to not assign to o.__annotations__.  But if you must, always set it to either a dict or None.
* Never delete o.__annotations__.
* Never modify o.__annotations__.
msg391562 - (view) Author: Larry Hastings (larry) * (Python committer) Date: 2021-04-21 23:34
> * Never modify o.__annotations__.

Let me revise that to:

* If o.__annotations__ is a dict, never modify the contents of that dict.
msg391572 - (view) Author: Guido van Rossum (Guido.van.Rossum) Date: 2021-04-22 04:07
I’d say that best practices for 3.9+ are more useful.

There seem to be several contradictions in your remarks, but we’ll get to those in the review.
msg391762 - (view) Author: Larry Hastings (larry) * (Python committer) Date: 2021-04-24 07:22
> I’d say that best practices for 3.9+ are more useful.

My point in writing this up was that the best practices change as of 3.10.  So, I could add a section to the Python 3.10 documentation giving best practices for 3.10+ and 3.9-.  But 3.9 and 3.10 have different best practices.
msg391781 - (view) Author: Guido van Rossum (Guido.van.Rossum) Date: 2021-04-24 14:59
And that’s a problem. There needs to be a recommendation on what to do for code that spans 3.9 and 3.10. What should users do otherwise? Drop 3.9 as soon as they introduce 3.10 support? Withhold 3.10 support until 3.9 reaches EOL?

IOW you can’t just break backward compatibility. (That’s why PEP 563 was rolled back in the first place.)
msg391802 - (view) Author: Larry Hastings (larry) * (Python committer) Date: 2021-04-24 21:10
I'm not breaking backwards compatibility--that's the point of all this.  But I'm improving the experience.  And if you don't care about 3.9 and before, you can stick to the new improved experience.

Looking in the class dict for annotations is terrible, but that's best practice for 3.9 and earlier because of the flawed design of annotations.

For 3.10+, best practice to look at annotations is inspect.get_annotations() for all objects.  But this isn't best practice for 3.9, because the function didn't exist in 3.9.  Also, the paragraph might go on to say, if you have a good reason why you can't call inspect.get_annotations(), best practice no longer requires you to look in the class dict, because of this very issue/PR that is behavior we are changing for 3.10.

So, best practices changed in 3.10.  And I plan to add a section to the docs somewhere that says "Here are best practices for accessing annotations in 3.10+".  As I said, if you think the Python 3.10 docs should have a section about "here are best practices in 3.9", I can add that too, but it'll be a different paragraph.
msg391809 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2021-04-24 21:49
It's find to have advice of "do X in 3.9", and "do Y in 3.10+". I think the issue is: if you have code that needs to run in 3.9 and 3.10+, what should you do? There needs to be some advice for that case.
msg391810 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2021-04-24 21:55
"It's fine to have advice ..."
msg391812 - (view) Author: Larry Hastings (larry) * (Python committer) Date: 2021-04-24 22:26
I'm please you folks are as supportive as you are of what I'm doing here, given that you seem a little unsure of the details.  I concede that there's a lot going on and it can be hard to keep it all in your head.

The point of this issue / PR is to improve the best practices for 3.10 without breaking the best practices for 3.9 and before.

Best practice in 3.10 is to use inspect.get_annotations(), and failing that, three-argument getattr(), for all annotated objects.  This issue / PR permits that to happen.

Best practices for 3.9 differed between different objects.  For classes, you had no choice but to look in the class dict because of the inheritance problem.  (Either that, or, the elaborate scheme 'attrs' used.)  That code will emphatically _still work_ in 3.10.  We are not breaking backwards compatibility.

So, the best practices in 3.9 and before will still work in 3.10.  But the best practices for 3.10+ are improved because of the work we're doing here and in other places.

My intent was to document the best practices for 3.10+.  If folks think it would be helpful, this same section in the 3.10 docs can also describe best practices for 3.9 and before.
msg391815 - (view) Author: Larry Hastings (larry) * (Python committer) Date: 2021-04-24 22:36
Aaaaand I just had a realization.  Lazy creation of an empty annotations dict, for both classes and modules, will work fine.

As stated in my previous comment in this issue, my goal here is to improve best practices in 3.10+, while preserving the unfortunate best practices of 3.9 and before.  I don't know why I couldn't see it before, but: adding a getset to modules and user classes, and lazily creating the empty annotations dict if not set, ought to work fine.  The getset code will have to store the annotations dict in the class / module dict but that's not a big deal.

If you're in 3.10+, and your code looks in the class dict for annotations, and we lazy-create it with a getset and store it in the class dict, then you still see what you expected to see.

* If the class has annotations, they'll be in the class dict.
* If the class has no annotations, but someone accessed
  cls.__annotations__, the empty dict will be lazily created
  and you'll see it.
* If the class doesn't have annotations, then the class dict
  won't have an '__annotations__' key, which is what you were expecting.

In any scenario the old best practices code works fine.

If you're in 3.10+, and your code uses cls.__annotations__ or getattr() to get the class dict, and we lazy-create it with a getset and store it in the class dict, then you also get what you expected.  If the class or module has annotations, you get them; if it doesn't, it lazy creates an empty dict and stores and returns that.  Your code works fine too.

Sorry I've been slow on figuring this out--but at least I got there in the end, before beta.  And it's good news!
msg391817 - (view) Author: Larry Hastings (larry) * (Python committer) Date: 2021-04-24 22:48
Hmm.  Sorry for the stream-of-consciousness thought process here, but this approach adds wrinkles too.

Function objects from the very beginning have lazy-created their annotations dict if it's not set.  Which means this works fine:

    while True:
        del fn.__annotations__
        print(fn.__annotations__)

You can do that all day.  The function object will *always* create a new annotations object if it doesn't have one.  del fn.__annotations__ always works, and accessing fn.__annotations__ always succeeds.  It doesn't retain any state of "I already lazily created one, I shouldn't create another".

If I add getsets to classes and modules so they lazy-create annotations on demand if they otherwise aren't set, then either a) they need an extra bit set somewhere that says "I lazily created an empty annotations dict once, I shouldn't do it again", or b) they will duplicate this behavior that functions display.

a) would better emulate existing semantics; b) would definitely be a user-visible breaking change.  There's actually a test in the Lib/test/test_opcodes (iirc) that explicitly tests deleting __annotations__, then checks that modifying the annotations dict throws an exception.  I haven't done any investigating to see if this check was the result of a regression, and there was a bpo issue, and there was a valid use case, etc etc etc.

My instinct is that deleting o.__annotations__ is not an important or widely-used use case.  In fact I plan to recommend against it in my "best practices" documentation.  So either approach should be acceptable.  Which means I should go with the simpler one, the one that will duplicate the function object's always-recreate-annotations-dicts behavior.
msg391819 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2021-04-24 23:08
Functions don't store __annotations__ in their __dict__, it is a separate slot named func_annotations (see funcobject.c). I guess that's because the __dict__ is purely for user-defined function attributes.

But perhaps for classes the C equivalent of this pseudo-code will work?

@property
def __annotations__(self):
    if "__annotations__" not in self.__dict__:
        self.__dict__["__annotations__"] = {}
    return self.__dict__["__annotations__"]

The whole thing is protected by the GIL of course, so there's no race condition between the check and the assignment.

So if you look in __dict__ it will be like it's still Python 3.9, but if you're using the attribute (the recommended approach for code that only cares about 3.10) it'll be as if it always existed. Sounds pretty compatible to me.
msg391820 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2021-04-24 23:18
So, honestly I don't understand what your concern with the lazy approach is. Was your design based on having a bit in the class/module object (outside its __dict__) saying "I already lazily created one"? Or am I missing something?

Also, I'll stop going on about "best practice".
msg391824 - (view) Author: Larry Hastings (larry) * (Python committer) Date: 2021-04-24 23:51
> Functions don't store __annotations__ in their __dict__, it is a
> separate slot named func_annotations (see funcobject.c). I guess
> that's because the __dict__ is purely for user-defined function
> attributes.

I brought up functions because I'm now proposing to make classes and modules behave more like functions in this one way, that they too will now lazy-create an empty annotations dict every time.  But yes, function objects don't store __annotations__ in their __dict__, and best practices for 3.9 and before was to use fn.__annotations__ or getattr() when getting the annotations from a function object, and 3.10 will not change that best practice.

I don't know specifically why the implementor made that design choice, but I might have done that too.  Most function objects don't have a __dict__ so this is almost always cheaper overall.  And recreating the dict seems harmless.

(Just to round the bases on this topic: I don't think you should be permitted to delete __annotations__, like most other metadata items on functions / classes / modules.  But I don't propose to change that for 3.10.)


> So if you look in __dict__ it will be like it's still Python 3.9,
> but if you're using the attribute (the recommended approach for code
> that only cares about 3.10) it'll be as if it always existed. Sounds
> pretty compatible to me.

Yes, exactly.  That was the thing I finally figured out this afternoon.  Sorry for being a slow learner.

Again, this approach will change the semantics around deleting annotations on class and module objects.  Deleting them won't be permanent--if you delete one, then ask for it, a fresh one will be created.  But that seems harmless.


> So, honestly I don't understand what your concern with the lazy
> approach is. Was your design based on having a bit in the
> class/module object (outside its __dict__) saying "I already
> lazily created one"? Or am I missing something?

My concern is that always lazy-creating on demand will change user-visible behavior.  Consider this code:

    class C:
        a:int=3

    del C.__annotations__
    print(C.__annotations__)

In 3.9, that throws an AttributeError, because C no longer has an '__annotations__' attribute.  If I change Python 3.10 so that classes and modules *always* lazy-create __annotations__ if they don't have them, then this code will succeed and print an empty dict.

That's a user-visible change, and I was hoping to avoid those entirely.  Is it a breaking change?  I doubt it.  Is it an important change?  It doesn't seem like it.  I bring it up just in the interests of considering every angle.  But I don't think this is important at all, and I think always lazy-creating annotations dicts on classes and modules is the right approach.
msg391825 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2021-04-25 00:01
I think "no user-visible changes" is a pipe dream. Deleting __annotations__ seems fairly pointless so I don't mind changes that are only visible when you do that.
msg392157 - (view) Author: Larry Hastings (larry) * (Python committer) Date: 2021-04-28 00:24
I think the PR is in good shape.  If anybody has the time for a review, I'd appreciate it!
msg392162 - (view) Author: Jelle Zijlstra (Jelle Zijlstra) * (Python triager) Date: 2021-04-28 05:02
For what it's worth, I checked grep.app and found only one usage of del on __annotations__, in a test suite: https://github.com/go-python/gpython/blob/master/py/tests/function.py#L82. Changing its behavior seems very low risk.
msg392373 - (view) Author: Larry Hastings (larry) * (Python committer) Date: 2021-04-30 03:09
New changeset 2f2b69855d6524e15d12c15ddc0adce629e7de84 by larryhastings in branch 'master':
bpo-43901: Lazy-create an empty annotations dict in all unannotated user classes and modules (#25623)
https://github.com/python/cpython/commit/2f2b69855d6524e15d12c15ddc0adce629e7de84
msg392374 - (view) Author: Larry Hastings (larry) * (Python committer) Date: 2021-04-30 03:10
Thanks for your feedback and reviews, everybody!  Python just got an eensy teensy tiny bit better.
msg392449 - (view) Author: Pablo Galindo Salgado (pablogsal) * (Python committer) Date: 2021-04-30 15:40
Unfortunately commit 2f2b69855d6524e15d12c15ddc0adce629e7de84 has broken the buildbots:

======================================================================
FAIL: test_annotations_are_created_correctly (test.test_module.ModuleTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "D:\buildarea\3.x.ware-win81-release.refleak\build\lib\test\test_module.py", line 338, in test_annotations_are_created_correctly
    self.assertTrue("__annotations__" in ann_module4.__dict__)
AssertionError: False is not true
----------------------------------------------------------------------

Example failure:

https://buildbot.python.org/all/#/builders/511/builds/12/steps/4/logs/stdio
msg392450 - (view) Author: Pablo Galindo Salgado (pablogsal) * (Python committer) Date: 2021-04-30 15:42
I think that test_annotations_are_created_correctly should use the import_helper.import_fresh_module helper
msg392452 - (view) Author: Larry Hastings (larry) * (Python committer) Date: 2021-04-30 15:47
Gee whiz!  Sorry about that.  I swear, it works fine on my machine.

I'll incorporate import_helper.import_fresh_module helper into the test as you suggest, and once I get it to work I'll send you a PR.  I don't know how to test fixing this failure, though, because again it already works fine on my machine.

Why would it fail on Windows?  Are there dramatic differences under the covers with respect to the import machinery and caching and such between POSIX and Windows?
msg392453 - (view) Author: Pablo Galindo Salgado (pablogsal) * (Python committer) Date: 2021-04-30 15:49
Oh, actually is workse because commenting out the test shows refleaks:

𓋹 ./python.exe -m test test_module -R :
0:00:00 load avg: 5.03 Run tests sequentially
0:00:00 load avg: 5.03 [1/1] test_module
beginning 9 repetitions
123456789
.........
test_module leaked [47, 47, 47, 47] references, sum=188
test_module leaked [6, 6, 6, 6] memory blocks, sum=24
test_module failed

== Tests result: FAILURE ==

1 test failed:
    test_module

Total duration: 2.0 sec
msg392454 - (view) Author: Pablo Galindo Salgado (pablogsal) * (Python committer) Date: 2021-04-30 15:50
>> Why would it fail on Windows?

It fails on all refleak buildbots. To reproduce:

% ./configure --with-pydebug -C && make -j -s
% ./python.exe -m test test_module -R :
msg392455 - (view) Author: Larry Hastings (larry) * (Python committer) Date: 2021-04-30 15:59
Eek!  I swear I did a refleak check and got a clean bill of health.  Again, sorry about this!
msg392457 - (view) Author: Pablo Galindo Salgado (pablogsal) * (Python committer) Date: 2021-04-30 16:01
> Eek!  I swear I did a refleak check and got a clean bill of health.  Again, sorry about this!

No problem at all! I'm sure we can fix this on time :)

Opened https://github.com/python/cpython/pull/25754 for the refleaks
msg392458 - (view) Author: Larry Hastings (larry) * (Python committer) Date: 2021-04-30 16:02
You want a separate PR for the refleak fix, or should I roll them both up into one?
msg392461 - (view) Author: Pablo Galindo Salgado (pablogsal) * (Python committer) Date: 2021-04-30 16:05
I made a fix for everything in https://github.com/python/cpython/pull/25754. Could you review it?
msg392469 - (view) Author: Pablo Galindo Salgado (pablogsal) * (Python committer) Date: 2021-04-30 16:26
New changeset e374a40afa09be728b01653a06c9febfad9c9c50 by Pablo Galindo in branch 'master':
bpo-43901: Fix refleaks in test_module (GH-25754)
https://github.com/python/cpython/commit/e374a40afa09be728b01653a06c9febfad9c9c50
msg392471 - (view) Author: Larry Hastings (larry) * (Python committer) Date: 2021-04-30 16:37
Obviously the original assertion failure ("AssertionError: False is not true") wasn't caused by the refleaks.  So I'm still puzzled about why that test worked on POSIX and failed on Windows.  I admit I was pulling some wacky import shenanigans in the test, and the import_fresh_module change is an improvement.  But still... what was going on?!
msg392476 - (view) Author: Pablo Galindo Salgado (pablogsal) * (Python committer) Date: 2021-04-30 16:46
> So I'm still puzzled about why that test worked on POSIX and failed on Windows

I was able to reproduce it in my MacOS machine (maybe also it reproduced on Linux). The problem is that when you run with -R, the test runs several times and the import statement is a noop the second time because is cached. We needed to modify the test to import the module always
msg392516 - (view) Author: Larry Hastings (larry) * (Python committer) Date: 2021-04-30 22:02
Ah, I see.  So it wasn't a Windows thing, it was a "we run the test multiple times and that particular test assumed it was only run once" thing.  And reflink testing is guaranteed to run every test multiple times.
msg392542 - (view) Author: Erlend E. Aasland (erlendaasland) * (Python triager) Date: 2021-04-30 23:27
test_grammar also needed a fix. It has been updated to use import_helper.import_fresh_module in bpo-43995 (GH-25764).
History
Date User Action Args
2021-04-30 23:27:35erlendaaslandsetnosy: + erlendaasland
messages: + msg392542
2021-04-30 22:02:03larrysetmessages: + msg392516
2021-04-30 16:46:37pablogsalsetmessages: + msg392476
2021-04-30 16:37:24larrysetmessages: + msg392471
2021-04-30 16:27:36pablogsalsetstatus: open -> closed
resolution: fixed
stage: patch review -> resolved
2021-04-30 16:26:48pablogsalsetmessages: + msg392469
2021-04-30 16:05:05pablogsalsetmessages: + msg392461
2021-04-30 16:02:25larrysetmessages: + msg392458
2021-04-30 16:01:29pablogsalsetmessages: + msg392457
2021-04-30 15:59:26larrysetmessages: + msg392455
2021-04-30 15:59:21pablogsalsetpull_requests: + pull_request24444
2021-04-30 15:57:58larrysetstage: resolved -> patch review
pull_requests: + pull_request24442
2021-04-30 15:50:09pablogsalsetmessages: + msg392454
2021-04-30 15:49:09pablogsalsetmessages: + msg392453
2021-04-30 15:47:31larrysetmessages: + msg392452
2021-04-30 15:42:21pablogsalsetmessages: + msg392450
2021-04-30 15:41:13pablogsalsetpriority: normal -> release blocker
2021-04-30 15:40:50pablogsalsetstatus: closed -> open

nosy: + pablogsal
messages: + msg392449

resolution: fixed -> (no value)
2021-04-30 03:10:40larrysetstatus: open -> closed
messages: + msg392374

assignee: larry
resolution: fixed
stage: patch review -> resolved
2021-04-30 03:09:14larrysetmessages: + msg392373
2021-04-28 05:02:14Jelle Zijlstrasetnosy: + Jelle Zijlstra
messages: + msg392162
2021-04-28 00:24:03larrysetmessages: + msg392157
2021-04-26 08:41:49koobssetnosy: - koobs
components: + Interpreter Core, - FreeBSD
2021-04-26 06:38:19kokxxxxiksetnosy: + koobs, kokxxxxik
components: + FreeBSD, - Interpreter Core
2021-04-26 05:55:57larrysetkeywords: + patch
stage: needs patch -> patch review
pull_requests: + pull_request24322
2021-04-25 00:01:08gvanrossumsetmessages: + msg391825
2021-04-24 23:51:50larrysetmessages: + msg391824
2021-04-24 23:18:38gvanrossumsetmessages: + msg391820
2021-04-24 23:08:59gvanrossumsetmessages: + msg391819
2021-04-24 22:48:47larrysetmessages: + msg391817
2021-04-24 22:36:55larrysettitle: Add an empty annotations dict to all unannotated classes and modules -> Lazy-create an empty annotations dict in all unannotated user classes and modules
2021-04-24 22:36:33larrysetmessages: + msg391815
2021-04-24 22:26:42larrysetmessages: + msg391812
2021-04-24 21:55:37eric.smithsetmessages: + msg391810
2021-04-24 21:49:21eric.smithsetmessages: + msg391809
2021-04-24 21:10:23larrysetmessages: + msg391802
2021-04-24 14:59:58Guido.van.Rossumsetmessages: + msg391781
2021-04-24 07:22:30larrysetmessages: + msg391762
2021-04-22 04:07:43Guido.van.Rossumsetnosy: + Guido.van.Rossum
messages: + msg391572
2021-04-21 23:34:29larrysetmessages: + msg391562
2021-04-21 23:14:26larrysetmessages: + msg391558
2021-04-21 22:59:40larrysetmessages: + msg391556
2021-04-21 22:56:30larrysetmessages: + msg391555
2021-04-21 20:26:23gvanrossumsetmessages: + msg391542
2021-04-21 20:15:35gvanrossumsetnosy: + gvanrossum
2021-04-21 07:28:50eric.smithsetnosy: + eric.smith
2021-04-21 07:26:22larrysetmessages: + msg391495
2021-04-21 06:53:37larrysetmessages: + msg391491
2021-04-21 06:45:21larrysettitle: Add an empty annotations dict to all classes and modules -> Add an empty annotations dict to all unannotated classes and modules
2021-04-21 06:44:01larrycreate