From 3e03d5292ec78548ef4fa0bcefd918c912f62577 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Mon, 10 Oct 2022 00:04:35 -0700 Subject: [PATCH 1/8] gh-96151: Use a private name for passing builtins to dataclass There's no indication that BUILTINS is a special name. Other names that are special to dataclass are all prefixed by an underscore. As mentioned in the issue, we can also avoid this locals dance altogether by using `().__class__.__base__` instead of `BUILTINS.object`. --- Lib/dataclasses.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index bf7f290af1622f..6bcdb769b166d4 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -431,8 +431,8 @@ def _create_fn(name, args, body, *, globals=None, locals=None, # worries about external callers. if locals is None: locals = {} - if 'BUILTINS' not in locals: - locals['BUILTINS'] = builtins + if '_BUILTINS' not in locals: + locals['_BUILTINS'] = builtins return_annotation = '' if return_type is not MISSING: locals['_return_type'] = return_type @@ -462,7 +462,7 @@ def _field_assign(frozen, name, value, self_name): # self_name is what "self" is called in this function: don't # hard-code "self", since that might be a field name. if frozen: - return f'BUILTINS.object.__setattr__({self_name},{name!r},{value})' + return f'_BUILTINS.object.__setattr__({self_name},{name!r},{value})' return f'{self_name}.{name}={value}' From 7f9f91e89a13b38eaa6d482602379614ba38142d Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Mon, 10 Oct 2022 07:07:34 +0000 Subject: [PATCH 2/8] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2022-10-10-07-07-31.gh-issue-96151.K9fwoq.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2022-10-10-07-07-31.gh-issue-96151.K9fwoq.rst diff --git a/Misc/NEWS.d/next/Library/2022-10-10-07-07-31.gh-issue-96151.K9fwoq.rst b/Misc/NEWS.d/next/Library/2022-10-10-07-07-31.gh-issue-96151.K9fwoq.rst new file mode 100644 index 00000000000000..700c9748735f8b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-10-10-07-07-31.gh-issue-96151.K9fwoq.rst @@ -0,0 +1 @@ +Allow ``BUILTINS`` to be a valid field name for frozen dataclasses. From 2d5f29026e2a0376edc5ac9563966dbbf5e27c64 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Mon, 10 Oct 2022 21:16:36 -0700 Subject: [PATCH 3/8] add a test case --- Lib/test/test_dataclasses.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_dataclasses.py b/Lib/test/test_dataclasses.py index 637c456dd49e7a..b10021b7da70c2 100644 --- a/Lib/test/test_dataclasses.py +++ b/Lib/test/test_dataclasses.py @@ -254,8 +254,10 @@ def test_field_named_object_frozen(self): @dataclass(frozen=True) class C: object: str - c = C('foo') + BUILTINS: int # gh-96151 + c = C('foo', 5) self.assertEqual(c.object, 'foo') + self.assertEqual(c.BUILTINS, 5) def test_field_named_like_builtin(self): # Attribute names can shadow built-in names From d7e87821382a78cf7a3111f692a0a6553f1298db Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Tue, 11 Oct 2022 14:50:41 -0700 Subject: [PATCH 4/8] separate out test --- Lib/test/test_dataclasses.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_dataclasses.py b/Lib/test/test_dataclasses.py index b10021b7da70c2..01e66840085bff 100644 --- a/Lib/test/test_dataclasses.py +++ b/Lib/test/test_dataclasses.py @@ -254,9 +254,15 @@ def test_field_named_object_frozen(self): @dataclass(frozen=True) class C: object: str - BUILTINS: int # gh-96151 - c = C('foo', 5) + c = C('foo') self.assertEqual(c.object, 'foo') + + def test_field_named_BUILTINS_frozen(self): + # gh-96151 + @dataclass(frozen=True) + class C: + BUILTINS: int + c = C(5) self.assertEqual(c.BUILTINS, 5) def test_field_named_like_builtin(self): From 96cc82b8e87a7e973988d7717ea7ed4270aa6aac Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sun, 16 Oct 2022 19:18:41 -0700 Subject: [PATCH 5/8] use dunder builtins --- Lib/dataclasses.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index 6bcdb769b166d4..94bf371bb15443 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -431,8 +431,8 @@ def _create_fn(name, args, body, *, globals=None, locals=None, # worries about external callers. if locals is None: locals = {} - if '_BUILTINS' not in locals: - locals['_BUILTINS'] = builtins + if '__BUILTINS__' not in locals: + locals['__BUILTINS__'] = builtins return_annotation = '' if return_type is not MISSING: locals['_return_type'] = return_type @@ -462,7 +462,7 @@ def _field_assign(frozen, name, value, self_name): # self_name is what "self" is called in this function: don't # hard-code "self", since that might be a field name. if frozen: - return f'_BUILTINS.object.__setattr__({self_name},{name!r},{value})' + return f'__BUILTINS__.object.__setattr__({self_name},{name!r},{value})' return f'{self_name}.{name}={value}' From e7456f899cae698b791b999d1cfb76d88cddf5ad Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sun, 30 Oct 2022 15:50:03 -0700 Subject: [PATCH 6/8] actually, prefix with __dataclass --- Lib/dataclasses.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index 94bf371bb15443..2eaffd23e9296b 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -431,8 +431,8 @@ def _create_fn(name, args, body, *, globals=None, locals=None, # worries about external callers. if locals is None: locals = {} - if '__BUILTINS__' not in locals: - locals['__BUILTINS__'] = builtins + if '__dataclass_builtins__' not in locals: + locals['__dataclass_builtins__'] = builtins return_annotation = '' if return_type is not MISSING: locals['_return_type'] = return_type @@ -462,7 +462,7 @@ def _field_assign(frozen, name, value, self_name): # self_name is what "self" is called in this function: don't # hard-code "self", since that might be a field name. if frozen: - return f'__BUILTINS__.object.__setattr__({self_name},{name!r},{value})' + return f'__dataclass_builtins__.object.__setattr__({self_name},{name!r},{value})' return f'{self_name}.{name}={value}' From 8f6e96850f59271a1824ee64bc16ab104bcf24e9 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sun, 30 Oct 2022 17:13:52 -0700 Subject: [PATCH 7/8] Remove builtins logic from _create_fn --- Lib/dataclasses.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index 2eaffd23e9296b..ffa4afa1c32a11 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -431,8 +431,6 @@ def _create_fn(name, args, body, *, globals=None, locals=None, # worries about external callers. if locals is None: locals = {} - if '__dataclass_builtins__' not in locals: - locals['__dataclass_builtins__'] = builtins return_annotation = '' if return_type is not MISSING: locals['_return_type'] = return_type @@ -462,7 +460,7 @@ def _field_assign(frozen, name, value, self_name): # self_name is what "self" is called in this function: don't # hard-code "self", since that might be a field name. if frozen: - return f'__dataclass_builtins__.object.__setattr__({self_name},{name!r},{value})' + return f'__dataclass_builtins_object__.__setattr__({self_name},{name!r},{value})' return f'{self_name}.{name}={value}' @@ -569,6 +567,7 @@ def _init_fn(fields, std_fields, kw_only_fields, frozen, has_post_init, locals.update({ 'MISSING': MISSING, '_HAS_DEFAULT_FACTORY': _HAS_DEFAULT_FACTORY, + '__dataclass_builtins_object__': object, }) body_lines = [] From 9d44429b8dca59ad0af3486db4f79586aad72926 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Mon, 31 Oct 2022 01:01:19 -0700 Subject: [PATCH 8/8] also update comment --- Lib/dataclasses.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index ffa4afa1c32a11..b54e16984cb15c 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -426,8 +426,8 @@ def wrapper(self): def _create_fn(name, args, body, *, globals=None, locals=None, return_type=MISSING): - # Note that we mutate locals when exec() is called. Caller - # beware! The only callers are internal to this module, so no + # Note that we may mutate locals. Callers beware! + # The only callers are internal to this module, so no # worries about external callers. if locals is None: locals = {}