Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Lib/dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,20 @@ def __repr__(self):
f'metadata={self.metadata}'
')')

# This is used to support the PEP 487 __set_name__ protocol in the
# case where we're using a field that contains a descriptor as a
# defaul value. For details on __set_name__, see
# https://www.python.org/dev/peps/pep-0487/#implementation-details.
# Note that in _process_class, this Field object is overwritten with
# 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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got here late, but shouldn't this check for __set_name__ on type(self.default) instead? That's how dunder protocols usually work, and it's also how typeobject.c itself implements __set_name__ (using _PyObject_LookupSpecial).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, thanks. I've created https://bugs.python.org/issue33175.

if func:
# There is a __set_name__ method on the descriptor,
# call it.
func(owner, name)


class _DataclassParams:
__slots__ = ('init',
Expand Down
42 changes: 42 additions & 0 deletions Lib/test/test_dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -2698,6 +2698,48 @@ class Derived(Base):
# We can add a new field to the derived instance.
d.z = 10

class TestDescriptors(unittest.TestCase):
def test_set_name(self):
# See bpo-33141.

# Create a descriptor.
class D:
def __set_name__(self, owner, name):
self.name = name
def __get__(self, instance, owner):
if instance is not None:
return 1
return self

# This is the case of just normal descriptor behavior, no
# dataclass code is involved in initializing the descriptor.
@dataclass
class C:
c: int=D()
self.assertEqual(C.c.name, 'c')

# Now test with a default value and init=False, which is the
# only time this is really meaningful. If not using
# init=False, then the descriptor will be overwritten, anyway.
@dataclass
class C:
c: int=field(default=D(), init=False)
self.assertEqual(C.c.name, 'c')
self.assertEqual(C().c, 1)

def test_non_descriptor(self):
# PEP 487 says __set_name__ should work on non-descriptors.
# Create a descriptor.

class D:
def __set_name__(self, owner, name):
self.name = name

@dataclass
class C:
c: int=field(default=D(), init=False)
self.assertEqual(C.c.name, 'c')


if __name__ == '__main__':
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Have Field objects pass through __set_name__ to their default values, if
they have their own __set_name__.