From 521995205a2cb6b504fe0e39af22a81f785350a3 Mon Sep 17 00:00:00 2001 From: "Eric V. Smith" Date: Thu, 29 Mar 2018 11:07:48 -0400 Subject: bpo-33175: dataclasses should look up __set_name__ on class, not instance (GH-6305) --- Lib/dataclasses.py | 4 +-- Lib/test/test_dataclasses.py | 39 +++++++++++++++++++--- .../2018-03-29-04-32-25.bpo-33175._zs1yM.rst | 2 ++ 3 files changed, 38 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2018-03-29-04-32-25.bpo-33175._zs1yM.rst diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index 8c197fe..bd7252c 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -248,11 +248,11 @@ class Field: # the default value, so the end result is a descriptor that had # __set_name__ called on it at the right time. def __set_name__(self, owner, name): - func = getattr(self.default, '__set_name__', None) + func = getattr(type(self.default), '__set_name__', None) if func: # There is a __set_name__ method on the descriptor, # call it. - func(owner, name) + func(self.default, owner, name) class _DataclassParams: diff --git a/Lib/test/test_dataclasses.py b/Lib/test/test_dataclasses.py index 2745eaf..5cd424c 100755 --- a/Lib/test/test_dataclasses.py +++ b/Lib/test/test_dataclasses.py @@ -2705,7 +2705,7 @@ class TestDescriptors(unittest.TestCase): # Create a descriptor. class D: def __set_name__(self, owner, name): - self.name = name + self.name = name + 'x' def __get__(self, instance, owner): if instance is not None: return 1 @@ -2716,7 +2716,7 @@ class TestDescriptors(unittest.TestCase): @dataclass class C: c: int=D() - self.assertEqual(C.c.name, 'c') + self.assertEqual(C.c.name, 'cx') # Now test with a default value and init=False, which is the # only time this is really meaningful. If not using @@ -2724,7 +2724,7 @@ class TestDescriptors(unittest.TestCase): @dataclass class C: c: int=field(default=D(), init=False) - self.assertEqual(C.c.name, 'c') + self.assertEqual(C.c.name, 'cx') self.assertEqual(C().c, 1) def test_non_descriptor(self): @@ -2733,12 +2733,41 @@ class TestDescriptors(unittest.TestCase): class D: def __set_name__(self, owner, name): - self.name = name + self.name = name + 'x' @dataclass class C: c: int=field(default=D(), init=False) - self.assertEqual(C.c.name, 'c') + self.assertEqual(C.c.name, 'cx') + + def test_lookup_on_instance(self): + # See bpo-33175. + class D: + pass + + d = D() + # Create an attribute on the instance, not type. + d.__set_name__ = Mock() + + # Make sure d.__set_name__ is not called. + @dataclass + class C: + i: int=field(default=d, init=False) + + self.assertEqual(d.__set_name__.call_count, 0) + + def test_lookup_on_class(self): + # See bpo-33175. + class D: + pass + D.__set_name__ = Mock() + + # Make sure D.__set_name__ is called. + @dataclass + class C: + i: int=field(default=D(), init=False) + + self.assertEqual(D.__set_name__.call_count, 1) if __name__ == '__main__': diff --git a/Misc/NEWS.d/next/Library/2018-03-29-04-32-25.bpo-33175._zs1yM.rst b/Misc/NEWS.d/next/Library/2018-03-29-04-32-25.bpo-33175._zs1yM.rst new file mode 100644 index 0000000..c872499 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-03-29-04-32-25.bpo-33175._zs1yM.rst @@ -0,0 +1,2 @@ +In dataclasses, Field.__set_name__ now looks up the __set_name__ special +method on the class, not the instance, of the default value. -- cgit v0.12