Skip to content

Commit de7a2f0

Browse files
authored
bpo-33141: Have dataclasses.Field pass through __set_name__ to any default argument. (GH-6260)
This is part of PEP 487 and the descriptor protocol.
1 parent 030345c commit de7a2f0

File tree

3 files changed

+58
-0
lines changed

3 files changed

+58
-0
lines changed

Lib/dataclasses.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,20 @@ def __repr__(self):
240240
f'metadata={self.metadata}'
241241
')')
242242

243+
# This is used to support the PEP 487 __set_name__ protocol in the
244+
# case where we're using a field that contains a descriptor as a
245+
# defaul value. For details on __set_name__, see
246+
# https://www.python.org/dev/peps/pep-0487/#implementation-details.
247+
# Note that in _process_class, this Field object is overwritten with
248+
# the default value, so the end result is a descriptor that had
249+
# __set_name__ called on it at the right time.
250+
def __set_name__(self, owner, name):
251+
func = getattr(self.default, '__set_name__', None)
252+
if func:
253+
# There is a __set_name__ method on the descriptor,
254+
# call it.
255+
func(owner, name)
256+
243257

244258
class _DataclassParams:
245259
__slots__ = ('init',

Lib/test/test_dataclasses.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2698,6 +2698,48 @@ class Derived(Base):
26982698
# We can add a new field to the derived instance.
26992699
d.z = 10
27002700

2701+
class TestDescriptors(unittest.TestCase):
2702+
def test_set_name(self):
2703+
# See bpo-33141.
2704+
2705+
# Create a descriptor.
2706+
class D:
2707+
def __set_name__(self, owner, name):
2708+
self.name = name
2709+
def __get__(self, instance, owner):
2710+
if instance is not None:
2711+
return 1
2712+
return self
2713+
2714+
# This is the case of just normal descriptor behavior, no
2715+
# dataclass code is involved in initializing the descriptor.
2716+
@dataclass
2717+
class C:
2718+
c: int=D()
2719+
self.assertEqual(C.c.name, 'c')
2720+
2721+
# Now test with a default value and init=False, which is the
2722+
# only time this is really meaningful. If not using
2723+
# init=False, then the descriptor will be overwritten, anyway.
2724+
@dataclass
2725+
class C:
2726+
c: int=field(default=D(), init=False)
2727+
self.assertEqual(C.c.name, 'c')
2728+
self.assertEqual(C().c, 1)
2729+
2730+
def test_non_descriptor(self):
2731+
# PEP 487 says __set_name__ should work on non-descriptors.
2732+
# Create a descriptor.
2733+
2734+
class D:
2735+
def __set_name__(self, owner, name):
2736+
self.name = name
2737+
2738+
@dataclass
2739+
class C:
2740+
c: int=field(default=D(), init=False)
2741+
self.assertEqual(C.c.name, 'c')
2742+
27012743

27022744
if __name__ == '__main__':
27032745
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Have Field objects pass through __set_name__ to their default values, if
2+
they have their own __set_name__.

0 commit comments

Comments
 (0)