Skip to content

Commit 15e2b64

Browse files
miss-islingtonJelleZijlstrajohnslavik
authored
[3.14] gh-148947: dataclasses: fix error on empty __class__ cell (GH-148948) (#148995)
gh-148947: dataclasses: fix error on empty __class__ cell (GH-148948) Also add a test demonstrating the need for the existing "is oldcls" check. (cherry picked from commit 6d7bbee) Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com> Co-authored-by: Bartosz Sławecki <bartosz@ilikepython.com>
1 parent 8acb98a commit 15e2b64

3 files changed

Lines changed: 59 additions & 3 deletions

File tree

Lib/dataclasses.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1292,10 +1292,18 @@ def _update_func_cell_for__class__(f, oldcls, newcls):
12921292
# This function doesn't reference __class__, so nothing to do.
12931293
return False
12941294
# Fix the cell to point to the new class, if it's already pointing
1295-
# at the old class. I'm not convinced that the "is oldcls" test
1296-
# is needed, but other than performance can't hurt.
1295+
# at the old class.
12971296
closure = f.__closure__[idx]
1298-
if closure.cell_contents is oldcls:
1297+
1298+
try:
1299+
contents = closure.cell_contents
1300+
except ValueError:
1301+
# Cell is empty
1302+
return False
1303+
1304+
# This check makes it so we avoid updating an incorrect cell if the
1305+
# class body contains a function that was defined in a different class.
1306+
if contents is oldcls:
12991307
closure.cell_contents = newcls
13001308
return True
13011309
return False

Lib/test/test_dataclasses/__init__.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5361,5 +5361,51 @@ def cls(self):
53615361
# one will be keeping a reference to the underlying class A.
53625362
self.assertIs(A().cls(), B)
53635363

5364+
def test_empty_class_cell(self):
5365+
# gh-148947: Make sure that we explicitly handle the empty class cell.
5366+
def maker():
5367+
if False:
5368+
__class__ = 42
5369+
5370+
def method(self):
5371+
return __class__
5372+
return method
5373+
5374+
from dataclasses import dataclass
5375+
5376+
@dataclass(slots=True)
5377+
class X:
5378+
a: int
5379+
5380+
meth = maker()
5381+
5382+
with self.assertRaisesRegex(NameError, '__class__'):
5383+
X(1).meth()
5384+
5385+
def test_class_cell_from_other_class(self):
5386+
# This test fails without the "is oldcls" check in
5387+
# _update_func_cell_for__class__.
5388+
class Base:
5389+
def meth(self):
5390+
return "Base"
5391+
5392+
class Child(Base):
5393+
def meth(self):
5394+
return super().meth() + " Child"
5395+
5396+
@dataclass(slots=True)
5397+
class DC(Child):
5398+
a: int
5399+
5400+
meth = Child.meth
5401+
5402+
closure = DC.meth.__closure__
5403+
self.assertEqual(len(closure), 1)
5404+
self.assertIs(closure[0].cell_contents, Child)
5405+
5406+
self.assertEqual(DC(1).meth(), "Base Child")
5407+
5408+
5409+
53645410
if __name__ == '__main__':
53655411
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix crash in :deco:`dataclasses.dataclass` with ``slots=True`` that occurred
2+
when a function found within the class had an empty ``__class__`` cell.

0 commit comments

Comments
 (0)