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
[Bug] CPython 3.14.2 + PEP 649 annotationlib.ForwardRef + PEP 484 self-reference = TypeError: unhashable type: 'cell' 😨 #610
...heh. Cells are how CPython implements closures at the C layer. Whenever you see the word "cell" in an exception traceback, your survival instinct should be to immediately run. 😂
@Glinte is correct, as always. This is almost certainly a CPython 3.14 issue. That said, there might be something @beartype can (and should) do to mitigate this issue. If the standard annotationlib API is failing @beartype for common use cases, @beartype should consider (A) detecting these cases and (B) circumventing them by using our own non-standard (but working) @beartype-specific forward reference proxies rather than standard (but non-working) annotationlib machinery.
Much to chew on here. Let us chew. And thanks so much to @Glinte for investigating this!
...lol. I cannot believe CPython devs failed to properly unit test the annotationlib.ForwardRef.__hash__() dunder method. Actually, wait. Hold my bear. I can believe it. It's all too easy to believe.
Based on @Glinte's superb analysis, this is clearly the problematic upstream line:
I was hoping upstream would have already resolved this, but... that doesn't appear to be the case. If we can cobble together something that works, @beartype could just monkey-patch this possibly broken annotationlib.ForwardRef.__hash__() implementation under older CPython versions.
Also, did anyone notice that that implementation is stupidly slow? Because it is. Which completely defeats the point of a hash function. Hash functions are supposed to be fast. That's how you guarantee amortized O(1) dictionary lookup. But this thing? It uselessly recomputes a huge recursive hash across the entire universe every time you call it. And the worst part is...
It doesn't even work. 🤦 🤦♂️ 🤦♀️ 🤦
Not quite sure what to do, honestly. First things first...
Upstream Needs to Know They Broke Stuff
It would be wonderful if someone who is not me could submit this to the upstream CPython issue tracker. It might've been fixed already, but I suspect it hasn't. I am sad face.
When someone who is not me reports this, please note that this worked under CPython 3.14.0. We could bisect exactly what went wrong if we cared, which we don't. But either CPython 3.14.1 or 3.14.2 introduced a regression that broke this use case.
Next things next...
I Can't Replicate This on My End
Your example works without raising the expected exception on my local devbox, @abhinand-c:
$ python3.14 run_abhinands_example.py
<__main__.MyClass object at 0x7fd7d5d65a90>
That looks sane. Admittedly, I'm using an older CPython 3.14 patch version:
$ python3.14 --version
Python 3.14.0
Interestingly, the annotationlib.ForwardRef.__hash__() implementation is somewhat different under this older CPython 3.14 patch version:
def__hash__(self):
returnhash((
self.__forward_arg__,
self.__forward_module__,
id(self.__globals__), # dictionaries are not hashable, so hash by identityself.__forward_is_class__,
self.__cell__,
self.__owner__,
tuple(sorted(self.__extra_names__.items())) ifself.__extra_names__elseNone,
))
So, simply self.__cell__ rather than tuple(sorted(self.__cell__.items())) if isinstance(self.__cell__, dict) else self.__cell__,. I don't quite get it, though. Even if I replace the former with the latter, this still works on my local devbox. Clearly, there is another problematic annotationlib change somewhere that intersects with this issue in mysterious ways that exhaust me.
Gods above! What has @beartype done to deserve this? I beseech thee.
changed the title [-][Bug] TypeError: unhashable type: 'cell' when using deferred annotations with @beartype on Python 3.14[/-][+][Bug] CPython 3.14.2 + PEP 649 `annotationlib.ForwardRef` + PEP 484 self-reference = `TypeError: unhashable type: 'cell'` 😨 [/+]on Jan 14, 2026
@leycec Yes, it seems that the issue is not there in Python3.14.0, but introduced in 3.14.1 and is still present in 3.14.2. Tested the versions locally in MacOS.
Wonderful. Thanks so much for going the extra mile. Until some combination of us and upstream resolves this, would you mind lightly refactoring your code? In theory, your example should work if you just move the @beartype decorator up from the method to its parent class: e.g.,
frombeartypeimportbeartype@beartype# <-- should work, but no idea. let's wring our hands as our forehead sweats.classMyClass:
deffoo(self) ->MyClass:
returnMyClass()
if__name__=="__main__":
print(MyClass().foo())
Does that work? If not, we're really in trouble. 😅
For anyone else running into this issue, I worked around it by reverting to the older string-based type annotation for the self-referencing return type. Using "MyClass" instead of the direct class reference avoids the error for now:
Wonderful. Thanks so much for going the extra mile. Until some combination of us and upstream resolves this, would you mind lightly refactoring your code? In theory, your example should work if you just move the @beartype decorator up from the method to its parent class: e.g.,
from beartype import beartype
@beartype # <-- should work, but no idea. let's wring our hands as our forehead sweats.
class MyClass:
def foo(self) -> MyClass:
return MyClass()
if name == "main":
print(MyClass().foo())
Does that work? If not, we're really in trouble. 😅
This one still throws the same error: TypeError: unhashable type: 'cell'
Indeed! Excellent workaround. If anyone's curious, the reason an old-school stringified forward reference works is that @beartype then has to use its own @beartype-specific forward reference proxy rather than reusing Python's generic forward reference proxy (i.e., annotationlib.ForwardRef object). Unlike Python, we test. That said:
This one still throws the same error: TypeError: unhashable type: 'cell'
OHNOES. That means self-references are super-broken under the newest CPython release. But that was the whole point of PEPs 649 and 749 and Python 3.14 itself! Our entire summer was wasted. Oh, well. That's life. Sometimes you waste an entire summer. It's unclear how exactly we can fix this on our end, because it's unclear how exactly CPython even broke this. For the moment:
Downstream users should consider blacklisting CPython 3.14.1 and 3.14.2 in their pyproject.toml.
Not a great solution, obviously. Actually, that's not a solution at all. 😭
Bwa-hah! Let's pretend I did something to resolve this for everyone. Instead, I did nothing. Pretty sure this issue is on CPython's end, but please do reopen if there's something @beartype can and/or should be doing for the QA masses here. 😅
Hope everyone's having a fantastic winter, by the winter. It's so bitterly cold here in the frozen wastes of the Canadian wilderness that... I can see my own breath while typing this. I am shivering. The cat is shivering. That's just how Canada rolls. Like a snowman with a puckered face.
Activity
Glinte commentedon Jan 13, 2026
Debugging information
Seems more likely to be an annotationlib problem because they defined
__hash__and included something non-hashableUpstream blame: python/cpython#138164 / python/cpython#140949 (3.14 port)
Cells are a C object that I've never heard of: https://docs.python.org/3/c-api/cell.html
leycec commentedon Jan 13, 2026
...heh. Cells are how CPython implements closures at the C layer. Whenever you see the word "cell" in an exception traceback, your survival instinct should be to immediately run. 😂
@Glinte is correct, as always. This is almost certainly a CPython 3.14 issue. That said, there might be something @beartype can (and should) do to mitigate this issue. If the standard
annotationlibAPI is failing @beartype for common use cases, @beartype should consider (A) detecting these cases and (B) circumventing them by using our own non-standard (but working) @beartype-specific forward reference proxies rather than standard (but non-working)annotationlibmachinery.Much to chew on here. Let us chew. And thanks so much to @Glinte for investigating this!
leycec commentedon Jan 14, 2026
...lol. I cannot believe CPython devs failed to properly unit test the
annotationlib.ForwardRef.__hash__()dunder method. Actually, wait. Hold my bear. I can believe it. It's all too easy to believe.Based on @Glinte's superb analysis, this is clearly the problematic upstream line:
I wonder why they thought that would ever work as advertised. Obviously:
sorted(self.__cell__.items())is also not guaranteed to be hashable.tuple(sorted(self.__cell__.items()))is likely to raiseTypeErrorexceptions in common use cases, which it does here.I facepalm two times. And then I facepalm two more times. 🤦 🤦♂️ 🤦♀️ 🤦
Upstream: Why You Do @beartype Dirty Like This?
I was hoping upstream would have already resolved this, but... that doesn't appear to be the case. If we can cobble together something that works, @beartype could just monkey-patch this possibly broken
annotationlib.ForwardRef.__hash__()implementation under older CPython versions.Also, did anyone notice that that implementation is stupidly slow? Because it is. Which completely defeats the point of a hash function. Hash functions are supposed to be fast. That's how you guarantee amortized
O(1)dictionary lookup. But this thing? It uselessly recomputes a huge recursive hash across the entire universe every time you call it. And the worst part is...It doesn't even work. 🤦 🤦♂️ 🤦♀️ 🤦
Not quite sure what to do, honestly. First things first...
Upstream Needs to Know They Broke Stuff
It would be wonderful if someone who is not me could submit this to the upstream CPython issue tracker. It might've been fixed already, but I suspect it hasn't. I am sad face.
When someone who is not me reports this, please note that this worked under CPython 3.14.0. We could bisect exactly what went wrong if we cared, which we don't. But either CPython 3.14.1 or 3.14.2 introduced a regression that broke this use case.
Next things next...
I Can't Replicate This on My End
Your example works without raising the expected exception on my local devbox, @abhinand-c:
That looks sane. Admittedly, I'm using an older CPython 3.14 patch version:
Interestingly, the
annotationlib.ForwardRef.__hash__()implementation is somewhat different under this older CPython 3.14 patch version:So, simply
self.__cell__rather thantuple(sorted(self.__cell__.items())) if isinstance(self.__cell__, dict) else self.__cell__,. I don't quite get it, though. Even if I replace the former with the latter, this still works on my local devbox. Clearly, there is another problematicannotationlibchange somewhere that intersects with this issue in mysterious ways that exhaust me.Gods above! What has @beartype done to deserve this? I beseech thee.
@beartype asks only this one question
Glinte commentedon Jan 14, 2026
I dont really want to make the upstream issue lol. and yes I reproduced on 3.14.2
[-][Bug] TypeError: unhashable type: 'cell' when using deferred annotations with @beartype on Python 3.14[/-][+][Bug] CPython 3.14.2 + PEP 649 `annotationlib.ForwardRef` + PEP 484 self-reference = `TypeError: unhashable type: 'cell'` 😨 [/+]leycec commentedon Jan 14, 2026
Right there with you. 🤣
Let's do nothing and pray things look better in the morning. I believe in the power of the couch potato.
abhinand-c commentedon Jan 14, 2026
@leycec Yes, it seems that the issue is not there in Python
3.14.0, but introduced in3.14.1and is still present in3.14.2. Tested the versions locally in MacOS.abhinand-c commentedon Jan 14, 2026
@leycec I'll go ahead and raise the issue in CPython.
leycec commentedon Jan 14, 2026
Wonderful. Thanks so much for going the extra mile. Until some combination of us and upstream resolves this, would you mind lightly refactoring your code? In theory, your example should work if you just move the
@beartypedecorator up from the method to its parent class: e.g.,Does that work? If not, we're really in trouble. 😅
abhinand-c commentedon Jan 14, 2026
For anyone else running into this issue, I worked around it by reverting to the older string-based type annotation for the self-referencing return type. Using "MyClass" instead of the direct class reference avoids the error for now:
abhinand-c commentedon Jan 14, 2026
This one still throws the same error:
TypeError: unhashable type: 'cell'leycec commentedon Jan 14, 2026
Indeed! Excellent workaround. If anyone's curious, the reason an old-school stringified forward reference works is that @beartype then has to use its own @beartype-specific forward reference proxy rather than reusing Python's generic forward reference proxy (i.e.,
annotationlib.ForwardRefobject). Unlike Python, we test. That said:OHNOES. That means self-references are super-broken under the newest CPython release. But that was the whole point of PEPs 649 and 749 and Python 3.14 itself! Our entire summer was wasted. Oh, well. That's life. Sometimes you waste an entire summer. It's unclear how exactly we can fix this on our end, because it's unclear how exactly CPython even broke this. For the moment:
pyproject.toml.Not a great solution, obviously. Actually, that's not a solution at all. 😭
abhinand-c commentedon Jan 14, 2026
@leycec @Glinte I have created the issue, please do review and comment: python/cpython#143831
abhinand-c commentedon Jan 15, 2026
python/cpython#143848 has been created by @johnslavik to address the issue. It has fixed the issue.
Also, any thoughts on how to improve the performance of the implementation.
Glinte commentedon Jan 15, 2026
I have no comment, lets hope leycec has something to say
leycec commentedon Jan 15, 2026
Lo, I have said things! But are they useful things? These tears of exhaustion say: "They'd better be." 🥹
leycec commentedon Feb 8, 2026
Bwa-hah! Let's pretend I did something to resolve this for everyone. Instead, I did nothing. Pretty sure this issue is on CPython's end, but please do reopen if there's something @beartype can and/or should be doing for the QA masses here. 😅
Hope everyone's having a fantastic winter, by the winter. It's so bitterly cold here in the frozen wastes of the Canadian wilderness that... I can see my own breath while typing this. I am shivering. The cat is shivering. That's just how Canada rolls. Like a snowman with a puckered face.
puckered-face snowman says: "sup, mah qa homies."