Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Lib/_collections_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,11 @@ async def _ag(): yield
### ONE-TRICK PONIES ###

def _check_methods(C, *methods):
mro = C.__mro__
mro_dicts = [B.__dict__ for B in C.__mro__]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should use a tuple instead of a list to reduce memory usage, since this collection isn’t modified:

Suggested change
mro_dicts = [B.__dict__ for B in C.__mro__]
mro_dicts = tuple(B.__dict__ for B in C.__mro__)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it be reasonable to cache this tuple for re-use?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But the caching here would need to be more sophisticated, since when the class is updated, the result must also change. Standard caching only takes the input into account, so in this case the input would be cached, but the output should actually differ depending on the class state.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can a class mro change after the fact?

I know the contents of dict could change but if the cache is just holding references to each class' dict itself, then any modifications of any dict should already be reflected in the cache

If the mro can change this becomes invalid

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to be clear I'm not suggesting to cache the result of this function, only the mro dict tuple

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, got it. I think caching the MRO dict tuple should be safe.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here you also need to decide what exactly we want to optimize, since a tuple gives about ~10% better memory efficiency, while a list is about ~10% faster when iterating in loops.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting, I didn't know the lists are faster to iterate thru, even with the length check at each iteration. TIL, thanks

In that case I'd opt for the list for top speed in the subclass check but I'm not sure how that fits into the best practices here or what the people want

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not worth debating tiny differences like this until we've verified that the change itself is worth pursing.

for method in methods:
for B in mro:
if method in B.__dict__:
if B.__dict__[method] is None:
for base_dict in mro_dicts:
if method in base_dict:
if base_dict[method] is None:
return NotImplemented
break
Comment on lines +111 to 115

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for base_dict in mro_dicts:
if method in base_dict:
if base_dict[method] is None:
return NotImplemented
break
for base_dict in mro_dicts:
method_impl = base_dict.get(method, sentinel)
if method_impl is None:
return NotImplemented
if method_impl is not sentinel:
break

This avoids calculating hash and looking for key in dict twice. sentinel object can be created at the top of the method.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That looks like a clear win, good eye! Might make sense to do the check optimistically, ie: consider the sentinel more likely than the None

if method_impl is sentinel:
    continue
if method_impl is None:
    return NotImplemented
break

else:
Expand Down
Loading