[Python-checkins] gh-98886: Fix issues with dataclass fields with special underscore names (#102032)

hauntsaninja webhook-mailer at python.org
Sat Mar 25 17:40:18 EDT 2023


https://github.com/python/cpython/commit/718e86671fe62a706c460b7f049b196e434cb5b3
commit: 718e86671fe62a706c460b7f049b196e434cb5b3
branch: main
author: Shantanu <12621235+hauntsaninja at users.noreply.github.com>
committer: hauntsaninja <12621235+hauntsaninja at users.noreply.github.com>
date: 2023-03-25T14:40:11-07:00
summary:

gh-98886: Fix issues with dataclass fields with special underscore names (#102032)

This commit prefixes `__dataclass` to several things in the locals dict:
- Names like `_dflt_` (which cause trouble, see first test)
- Names like `_type_` (not known to be able to cause trouble)
- `_return_type` (not known to able to cause trouble)
- `_HAS_DEFAULT_FACTORY` (which causes trouble, see second test)

In addition, this removes `MISSING` from the locals dict. As far as I can tell, this wasn't needed even in the initial implementation of dataclasses.py (and tests on that version passed with it removed). This makes me wary :-)

This is basically a continuation of #96151, where fixing this was welcomed in https://github.com/python/cpython/pull/98143#issuecomment-1280306360

files:
A Misc/NEWS.d/next/Library/2023-02-18-23-03-50.gh-issue-98886.LkKGWv.rst
M Lib/dataclasses.py
M Lib/test/test_dataclasses.py

diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py
index 0e04469be3ca..7558287bad44 100644
--- a/Lib/dataclasses.py
+++ b/Lib/dataclasses.py
@@ -432,8 +432,8 @@ def _create_fn(name, args, body, *, globals=None, locals=None,
         locals = {}
     return_annotation = ''
     if return_type is not MISSING:
-        locals['_return_type'] = return_type
-        return_annotation = '->_return_type'
+        locals['__dataclass_return_type__'] = return_type
+        return_annotation = '->__dataclass_return_type__'
     args = ','.join(args)
     body = '\n'.join(f'  {b}' for b in body)
 
@@ -467,14 +467,14 @@ def _field_init(f, frozen, globals, self_name, slots):
     # Return the text of the line in the body of __init__ that will
     # initialize this field.
 
-    default_name = f'_dflt_{f.name}'
+    default_name = f'__dataclass_dflt_{f.name}__'
     if f.default_factory is not MISSING:
         if f.init:
             # This field has a default factory.  If a parameter is
             # given, use it.  If not, call the factory.
             globals[default_name] = f.default_factory
             value = (f'{default_name}() '
-                     f'if {f.name} is _HAS_DEFAULT_FACTORY '
+                     f'if {f.name} is __dataclass_HAS_DEFAULT_FACTORY__ '
                      f'else {f.name}')
         else:
             # This is a field that's not in the __init__ params, but
@@ -535,11 +535,11 @@ def _init_param(f):
     elif f.default is not MISSING:
         # There's a default, this will be the name that's used to look
         # it up.
-        default = f'=_dflt_{f.name}'
+        default = f'=__dataclass_dflt_{f.name}__'
     elif f.default_factory is not MISSING:
         # There's a factory function.  Set a marker.
-        default = '=_HAS_DEFAULT_FACTORY'
-    return f'{f.name}:_type_{f.name}{default}'
+        default = '=__dataclass_HAS_DEFAULT_FACTORY__'
+    return f'{f.name}:__dataclass_type_{f.name}__{default}'
 
 
 def _init_fn(fields, std_fields, kw_only_fields, frozen, has_post_init,
@@ -562,10 +562,9 @@ def _init_fn(fields, std_fields, kw_only_fields, frozen, has_post_init,
                 raise TypeError(f'non-default argument {f.name!r} '
                                 'follows default argument')
 
-    locals = {f'_type_{f.name}': f.type for f in fields}
+    locals = {f'__dataclass_type_{f.name}__': f.type for f in fields}
     locals.update({
-        'MISSING': MISSING,
-        '_HAS_DEFAULT_FACTORY': _HAS_DEFAULT_FACTORY,
+        '__dataclass_HAS_DEFAULT_FACTORY__': _HAS_DEFAULT_FACTORY,
         '__dataclass_builtins_object__': object,
     })
 
diff --git a/Lib/test/test_dataclasses.py b/Lib/test/test_dataclasses.py
index affd9cede19c..6888680105c4 100644
--- a/Lib/test/test_dataclasses.py
+++ b/Lib/test/test_dataclasses.py
@@ -285,6 +285,23 @@ class C:
         c = C(5)
         self.assertEqual(c.BUILTINS, 5)
 
+    def test_field_with_special_single_underscore_names(self):
+        # gh-98886
+
+        @dataclass
+        class X:
+            x: int = field(default_factory=lambda: 111)
+            _dflt_x: int = field(default_factory=lambda: 222)
+
+        X()
+
+        @dataclass
+        class Y:
+            y: int = field(default_factory=lambda: 111)
+            _HAS_DEFAULT_FACTORY: int = 222
+
+        assert Y(y=222).y == 222
+
     def test_field_named_like_builtin(self):
         # Attribute names can shadow built-in names
         # since code generation is used.
diff --git a/Misc/NEWS.d/next/Library/2023-02-18-23-03-50.gh-issue-98886.LkKGWv.rst b/Misc/NEWS.d/next/Library/2023-02-18-23-03-50.gh-issue-98886.LkKGWv.rst
new file mode 100644
index 000000000000..64e4d6eed2f6
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-02-18-23-03-50.gh-issue-98886.LkKGWv.rst
@@ -0,0 +1 @@
+Fix issues when defining dataclasses that have fields with specific underscore names that aren't clearly reserved by :mod:`dataclasses`.



More information about the Python-checkins mailing list