[Python-checkins] bpo-33141: Have dataclasses.Field pass through __set_name__ to any default argument. (GH-6260)

Eric V. Smith webhook-mailer at python.org
Mon Mar 26 13:29:19 EDT 2018


https://github.com/python/cpython/commit/de7a2f04d6b9427d568fcb43b6f512f9b4c4bd84
commit: de7a2f04d6b9427d568fcb43b6f512f9b4c4bd84
branch: master
author: Eric V. Smith <ericvsmith at users.noreply.github.com>
committer: GitHub <noreply at github.com>
date: 2018-03-26T13:29:16-04:00
summary:

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.

files:
A Misc/NEWS.d/next/Library/2018-03-26-12-33-13.bpo-33141.23wlxf.rst
M Lib/dataclasses.py
M Lib/test/test_dataclasses.py

diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py
index 8ccc4c88aeca..8c197fe73904 100644
--- a/Lib/dataclasses.py
+++ b/Lib/dataclasses.py
@@ -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)
+        if func:
+            # There is a __set_name__ method on the descriptor,
+            #  call it.
+            func(owner, name)
+
 
 class _DataclassParams:
     __slots__ = ('init',
diff --git a/Lib/test/test_dataclasses.py b/Lib/test/test_dataclasses.py
index f7f132ca30d2..2745eaf6893b 100755
--- a/Lib/test/test_dataclasses.py
+++ b/Lib/test/test_dataclasses.py
@@ -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()
diff --git a/Misc/NEWS.d/next/Library/2018-03-26-12-33-13.bpo-33141.23wlxf.rst b/Misc/NEWS.d/next/Library/2018-03-26-12-33-13.bpo-33141.23wlxf.rst
new file mode 100644
index 000000000000..1d49c08fed54
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2018-03-26-12-33-13.bpo-33141.23wlxf.rst
@@ -0,0 +1,2 @@
+Have Field objects pass through __set_name__ to their default values, if
+they have their own __set_name__.



More information about the Python-checkins mailing list