Skip to content

bpo-34213: frozen dataclass with "object" attr bug #8452

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 12, 2018
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
8 changes: 7 additions & 1 deletion Lib/dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import types
import inspect
import keyword
import builtins

__all__ = ['dataclass',
'field',
Expand Down Expand Up @@ -343,6 +344,11 @@ def _create_fn(name, args, body, *, globals=None, locals=None,
# worries about external callers.
if locals is None:
locals = {}
# __builtins__ may be the "builtins" module or
# the value of its "__dict__",
# so make sure "__builtins__" is the module.
if globals is not None and '__builtins__' not in globals:
globals['__builtins__'] = builtins
return_annotation = ''
if return_type is not MISSING:
locals['_return_type'] = return_type
Expand All @@ -365,7 +371,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'object.__setattr__({self_name},{name!r},{value})'
return f'__builtins__.object.__setattr__({self_name},{name!r},{value})'
return f'{self_name}.{name}={value}'


Expand Down
50 changes: 50 additions & 0 deletions Lib/test/test_dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import pickle
import inspect
import builtins
import unittest
from unittest.mock import Mock
from typing import ClassVar, Any, List, Union, Tuple, Dict, Generic, TypeVar, Optional
Expand Down Expand Up @@ -192,6 +193,55 @@ class C:
first = next(iter(sig.parameters))
self.assertEqual('self', first)

def test_field_named_object(self):
@dataclass
class C:
object: str
c = C('foo')
self.assertEqual(c.object, 'foo')

def test_field_named_object_frozen(self):
@dataclass(frozen=True)
class C:
object: str
c = C('foo')
self.assertEqual(c.object, 'foo')

def test_field_named_like_builtin(self):
# Attribute names can shadow built-in names
# since code generation is used.
# Ensure that this is not happening.
exclusions = {'None', 'True', 'False'}
builtins_names = sorted(
b for b in builtins.__dict__.keys()
if not b.startswith('__') and b not in exclusions
)
attributes = [(name, str) for name in builtins_names]
C = make_dataclass('C', attributes)

c = C(*[name for name in builtins_names])

for name in builtins_names:
self.assertEqual(getattr(c, name), name)

def test_field_named_like_builtin_frozen(self):
# Attribute names can shadow built-in names
# since code generation is used.
# Ensure that this is not happening
# for frozen data classes.
exclusions = {'None', 'True', 'False'}
builtins_names = sorted(
b for b in builtins.__dict__.keys()
if not b.startswith('__') and b not in exclusions
)
attributes = [(name, str) for name in builtins_names]
C = make_dataclass('C', attributes, frozen=True)

c = C(*[name for name in builtins_names])

for name in builtins_names:
self.assertEqual(getattr(c, name), name)

def test_0_field_compare(self):
# Ensure that order=False is the default.
@dataclass
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Allow frozen dataclasses to have a field named "object". Previously this conflicted with an internal use of "object".