From 9983346ba92d5aa2735a0dfa0d6ff5b76bf50f2d Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 23 Apr 2026 21:47:53 -0700 Subject: [PATCH 1/2] gh-148947: dataclasses: fix crash on empty __class__ cell Also add a test demonstrating the need for the existing "is oldcls" check. --- Lib/dataclasses.py | 14 ++++-- Lib/test/test_dataclasses/__init__.py | 46 +++++++++++++++++++ ...-04-23-21-47-49.gh-issue-148947.W4V2lG.rst | 2 + 3 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-04-23-21-47-49.gh-issue-148947.W4V2lG.rst diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index 9d5bed6b96fc49..988edfed6f4dcb 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -1298,10 +1298,18 @@ def _update_func_cell_for__class__(f, oldcls, newcls): # This function doesn't reference __class__, so nothing to do. return False # Fix the cell to point to the new class, if it's already pointing - # at the old class. I'm not convinced that the "is oldcls" test - # is needed, but other than performance can't hurt. + # at the old class. closure = f.__closure__[idx] - if closure.cell_contents is oldcls: + + try: + contents = closure.cell_contents + except ValueError: + # Cell is empty + return False + + # This check makes it so we avoid updating an incorrect cell if the + # class body contains a function that was defined in a different class. + if contents is oldcls: closure.cell_contents = newcls return True return False diff --git a/Lib/test/test_dataclasses/__init__.py b/Lib/test/test_dataclasses/__init__.py index e0cfe3df3e6357..f235ab1086f996 100644 --- a/Lib/test/test_dataclasses/__init__.py +++ b/Lib/test/test_dataclasses/__init__.py @@ -5375,5 +5375,51 @@ def cls(self): # one will be keeping a reference to the underlying class A. self.assertIs(A().cls(), B) + def test_empty_class_cell(self): + # gh-148947: Make sure that if the class cell is empty, we don't crash. + def maker(): + if False: + __class__ = 42 + + def method(self): + return __class__ + return method + + from dataclasses import dataclass + + @dataclass(slots=True) + class X: + a: int + + meth = maker() + + with self.assertRaisesRegex(NameError, '__class__'): + X(1).meth() + + def test_class_cell_from_other_class(self): + # This test fails without the "is oldcls" check in + # _update_func_cell_for__class__. + class Base: + def meth(self): + return "Base" + + class Child(Base): + def meth(self): + return super().meth() + " Child" + + @dataclass(slots=True) + class DC(Child): + a: int + + meth = Child.meth + + closure = DC.meth.__closure__ + self.assertEqual(len(closure), 1) + self.assertIs(closure[0].cell_contents, Child) + + self.assertEqual(DC(1).meth(), "Base Child") + + + if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Library/2026-04-23-21-47-49.gh-issue-148947.W4V2lG.rst b/Misc/NEWS.d/next/Library/2026-04-23-21-47-49.gh-issue-148947.W4V2lG.rst new file mode 100644 index 00000000000000..f9783266f5cc42 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-04-23-21-47-49.gh-issue-148947.W4V2lG.rst @@ -0,0 +1,2 @@ +Fix crash in :deco:`dataclasses.dataclass` with ``slots=True`` that occurred +when a function found within the class had an empty ``__class__`` cell. From f3792ab96f82594a5c885f69b06244c20eee2479 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Fri, 24 Apr 2026 06:09:40 -0700 Subject: [PATCH 2/2] Update Lib/test/test_dataclasses/__init__.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartosz Sławecki --- Lib/test/test_dataclasses/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_dataclasses/__init__.py b/Lib/test/test_dataclasses/__init__.py index f235ab1086f996..6ff82b8810abed 100644 --- a/Lib/test/test_dataclasses/__init__.py +++ b/Lib/test/test_dataclasses/__init__.py @@ -5376,7 +5376,7 @@ def cls(self): self.assertIs(A().cls(), B) def test_empty_class_cell(self): - # gh-148947: Make sure that if the class cell is empty, we don't crash. + # gh-148947: Make sure that we explicitly handle the empty class cell. def maker(): if False: __class__ = 42