bpo-33175: dataclasses should look up __set_name__ on class, not instance (GH-6305)

This commit is contained in:
Eric V. Smith 2018-03-29 11:07:48 -04:00 committed by GitHub
parent b9e7fe38a0
commit 521995205a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 38 additions and 7 deletions

View File

@ -248,11 +248,11 @@ class Field:
# the default value, so the end result is a descriptor that had # the default value, so the end result is a descriptor that had
# __set_name__ called on it at the right time. # __set_name__ called on it at the right time.
def __set_name__(self, owner, name): def __set_name__(self, owner, name):
func = getattr(self.default, '__set_name__', None) func = getattr(type(self.default), '__set_name__', None)
if func: if func:
# There is a __set_name__ method on the descriptor, # There is a __set_name__ method on the descriptor,
# call it. # call it.
func(owner, name) func(self.default, owner, name)
class _DataclassParams: class _DataclassParams:

View File

@ -2705,7 +2705,7 @@ class TestDescriptors(unittest.TestCase):
# Create a descriptor. # Create a descriptor.
class D: class D:
def __set_name__(self, owner, name): def __set_name__(self, owner, name):
self.name = name self.name = name + 'x'
def __get__(self, instance, owner): def __get__(self, instance, owner):
if instance is not None: if instance is not None:
return 1 return 1
@ -2716,7 +2716,7 @@ class TestDescriptors(unittest.TestCase):
@dataclass @dataclass
class C: class C:
c: int=D() 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 # Now test with a default value and init=False, which is the
# only time this is really meaningful. If not using # only time this is really meaningful. If not using
@ -2724,7 +2724,7 @@ class TestDescriptors(unittest.TestCase):
@dataclass @dataclass
class C: class C:
c: int=field(default=D(), init=False) c: int=field(default=D(), init=False)
self.assertEqual(C.c.name, 'c') self.assertEqual(C.c.name, 'cx')
self.assertEqual(C().c, 1) self.assertEqual(C().c, 1)
def test_non_descriptor(self): def test_non_descriptor(self):
@ -2733,12 +2733,41 @@ class TestDescriptors(unittest.TestCase):
class D: class D:
def __set_name__(self, owner, name): def __set_name__(self, owner, name):
self.name = name self.name = name + 'x'
@dataclass @dataclass
class C: class C:
c: int=field(default=D(), init=False) 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__': if __name__ == '__main__':

View File

@ -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.