Skip to content

Conversation

@johnslavik
Copy link
Member

@johnslavik johnslavik commented Jan 14, 2026

Cells are mutable and, in regular comparison, they are equal when their contents are equal (or if both are empty).

Two different cells should not be considered equal by value in forward references, though.
If there are two separate cells, they either (1) target different variables or (2) belong to different scopes, so two separate forward references storing two separate cells are conceptually unequal even if these cells happen to be equal at a given point in time. Mutability, essentially.

I'm still working on tests.

self.__forward_is_class__,
tuple(sorted(self.__cell__.items())) if isinstance(self.__cell__, dict) else self.__cell__,
( # cells are mutable and not hashable as well
tuple(sorted([(name, id(cell)) for name, cell in self.__cell__.items()]))

Choose a reason for hiding this comment

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

@johnslavik Do we need the list comprehension here? We could pass a generator directly to sorted() for slightly better efficiency:

tuple(sorted((name, id(cell)) for name, cell in self.__cell__.items()))

Also, is there any way to avoid sorting altogether without compromising the stability or correctness of the hash?

Copy link
Member Author

@johnslavik johnslavik Jan 15, 2026

Choose a reason for hiding this comment

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

Do we need the list comprehension here? We could pass a generator directly to sorted() for slightly better efficiency:

tuple(sorted((name, id(cell)) for name, cell in self.__cell__.items()))

This is not necessarily more efficient.

List comprehensions are inlined since PEP 709. I've used a list comprehension on purpose here because cell dicts would never be big -- walking a new transient generator on small data is more expensive than computing an eager list. See also #143825 (comment).

Also, is there any way to avoid sorting altogether without compromising the stability or correctness of the hash?

How does sorting here compromise stability or correctness?

This code was meant to sort cell dicts. Although this sounds like an unlikely situation, I think it makes sense for hashes of two different cell dicts that happen to point to identical cells to be the same.

@leycec

This comment was marked as resolved.

@johnslavik
Copy link
Member Author

johnslavik commented Jan 15, 2026

Thank you for your interest in this! I encourage you to open a new issue if you think like the performance of ForwardRef.__hash__ can and should be improved. I'm afraid it is out of scope of this surgical PR -- I will not be addressing anything outside the topic of cell comparison.


Regarding why I solved it this way -- I just went with what the original intent was but with a corrected assumption about the hashability of cells.

An alternative approach to hashing a sorted tuple of names -> cell ids is to simply return an identity of __cell__. It can make sense because assuming cell dicts change additively -- so if a forwardref has the same hash because of the same forward arg and the same cell dict, then even if the cell dict changed over time, it possibly only added new keys, so evaluating the ref wouldn't produce different results. I also assume two different dict objects with same cell ids to be rather rare.

cc @JelleZijlstra

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs backport to 3.14 bugs and security fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants