Skip to content

Commit 5219952

Browse files
authored
bpo-33175: dataclasses should look up __set_name__ on class, not instance (GH-6305)
1 parent b9e7fe3 commit 5219952

File tree

3 files changed

+38
-7
lines changed

3 files changed

+38
-7
lines changed

Lib/dataclasses.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,11 +248,11 @@ def __repr__(self):
248248
# the default value, so the end result is a descriptor that had
249249
# __set_name__ called on it at the right time.
250250
def __set_name__(self, owner, name):
251-
func = getattr(self.default, '__set_name__', None)
251+
func = getattr(type(self.default), '__set_name__', None)
252252
if func:
253253
# There is a __set_name__ method on the descriptor,
254254
# call it.
255-
func(owner, name)
255+
func(self.default, owner, name)
256256

257257

258258
class _DataclassParams:

Lib/test/test_dataclasses.py

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2705,7 +2705,7 @@ def test_set_name(self):
27052705
# Create a descriptor.
27062706
class D:
27072707
def __set_name__(self, owner, name):
2708-
self.name = name
2708+
self.name = name + 'x'
27092709
def __get__(self, instance, owner):
27102710
if instance is not None:
27112711
return 1
@@ -2716,15 +2716,15 @@ def __get__(self, instance, owner):
27162716
@dataclass
27172717
class C:
27182718
c: int=D()
2719-
self.assertEqual(C.c.name, 'c')
2719+
self.assertEqual(C.c.name, 'cx')
27202720

27212721
# Now test with a default value and init=False, which is the
27222722
# only time this is really meaningful. If not using
27232723
# init=False, then the descriptor will be overwritten, anyway.
27242724
@dataclass
27252725
class C:
27262726
c: int=field(default=D(), init=False)
2727-
self.assertEqual(C.c.name, 'c')
2727+
self.assertEqual(C.c.name, 'cx')
27282728
self.assertEqual(C().c, 1)
27292729

27302730
def test_non_descriptor(self):
@@ -2733,12 +2733,41 @@ def test_non_descriptor(self):
27332733

27342734
class D:
27352735
def __set_name__(self, owner, name):
2736-
self.name = name
2736+
self.name = name + 'x'
27372737

27382738
@dataclass
27392739
class C:
27402740
c: int=field(default=D(), init=False)
2741-
self.assertEqual(C.c.name, 'c')
2741+
self.assertEqual(C.c.name, 'cx')
2742+
2743+
def test_lookup_on_instance(self):
2744+
# See bpo-33175.
2745+
class D:
2746+
pass
2747+
2748+
d = D()
2749+
# Create an attribute on the instance, not type.
2750+
d.__set_name__ = Mock()
2751+
2752+
# Make sure d.__set_name__ is not called.
2753+
@dataclass
2754+
class C:
2755+
i: int=field(default=d, init=False)
2756+
2757+
self.assertEqual(d.__set_name__.call_count, 0)
2758+
2759+
def test_lookup_on_class(self):
2760+
# See bpo-33175.
2761+
class D:
2762+
pass
2763+
D.__set_name__ = Mock()
2764+
2765+
# Make sure D.__set_name__ is called.
2766+
@dataclass
2767+
class C:
2768+
i: int=field(default=D(), init=False)
2769+
2770+
self.assertEqual(D.__set_name__.call_count, 1)
27422771

27432772

27442773
if __name__ == '__main__':
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
In dataclasses, Field.__set_name__ now looks up the __set_name__ special
2+
method on the class, not the instance, of the default value.

0 commit comments

Comments
 (0)