Skip to content

Commit 6d7bbee

Browse files
gh-148947: dataclasses: fix error on empty __class__ cell (#148948)
Also add a test demonstrating the need for the existing "is oldcls" check. Co-authored-by: Bartosz Sławecki <bartosz@ilikepython.com>
1 parent 5ea3ae7 commit 6d7bbee

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
@@ -1298,10 +1298,18 @@ def _update_func_cell_for__class__(f, oldcls, newcls):
12981298
# This function doesn't reference __class__, so nothing to do.
12991299
return False
13001300
# Fix the cell to point to the new class, if it's already pointing
1301-
# at the old class. I'm not convinced that the "is oldcls" test
1302-
# is needed, but other than performance can't hurt.
1301+
# at the old class.
13031302
closure = f.__closure__[idx]
1304-
if closure.cell_contents is oldcls:
1303+
1304+
try:
1305+
contents = closure.cell_contents
1306+
except ValueError:
1307+
# Cell is empty
1308+
return False
1309+
1310+
# This check makes it so we avoid updating an incorrect cell if the
1311+
# class body contains a function that was defined in a different class.
1312+
if contents is oldcls:
13051313
closure.cell_contents = newcls
13061314
return True
13071315
return False

Lib/test/test_dataclasses/__init__.py

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

5378+
def test_empty_class_cell(self):
5379+
# gh-148947: Make sure that we explicitly handle the empty class cell.
5380+
def maker():
5381+
if False:
5382+
__class__ = 42
5383+
5384+
def method(self):
5385+
return __class__
5386+
return method
5387+
5388+
from dataclasses import dataclass
5389+
5390+
@dataclass(slots=True)
5391+
class X:
5392+
a: int
5393+
5394+
meth = maker()
5395+
5396+
with self.assertRaisesRegex(NameError, '__class__'):
5397+
X(1).meth()
5398+
5399+
def test_class_cell_from_other_class(self):
5400+
# This test fails without the "is oldcls" check in
5401+
# _update_func_cell_for__class__.
5402+
class Base:
5403+
def meth(self):
5404+
return "Base"
5405+
5406+
class Child(Base):
5407+
def meth(self):
5408+
return super().meth() + " Child"
5409+
5410+
@dataclass(slots=True)
5411+
class DC(Child):
5412+
a: int
5413+
5414+
meth = Child.meth
5415+
5416+
closure = DC.meth.__closure__
5417+
self.assertEqual(len(closure), 1)
5418+
self.assertIs(closure[0].cell_contents, Child)
5419+
5420+
self.assertEqual(DC(1).meth(), "Base Child")
5421+
5422+
5423+
53785424
if __name__ == '__main__':
53795425
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)