mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
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
This commit is contained in:
parent
027223db96
commit
718e86671f
3 changed files with 27 additions and 10 deletions
|
@ -432,8 +432,8 @@ def _create_fn(name, args, body, *, globals=None, locals=None,
|
||||||
locals = {}
|
locals = {}
|
||||||
return_annotation = ''
|
return_annotation = ''
|
||||||
if return_type is not MISSING:
|
if return_type is not MISSING:
|
||||||
locals['_return_type'] = return_type
|
locals['__dataclass_return_type__'] = return_type
|
||||||
return_annotation = '->_return_type'
|
return_annotation = '->__dataclass_return_type__'
|
||||||
args = ','.join(args)
|
args = ','.join(args)
|
||||||
body = '\n'.join(f' {b}' for b in body)
|
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
|
# Return the text of the line in the body of __init__ that will
|
||||||
# initialize this field.
|
# 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.default_factory is not MISSING:
|
||||||
if f.init:
|
if f.init:
|
||||||
# This field has a default factory. If a parameter is
|
# This field has a default factory. If a parameter is
|
||||||
# given, use it. If not, call the factory.
|
# given, use it. If not, call the factory.
|
||||||
globals[default_name] = f.default_factory
|
globals[default_name] = f.default_factory
|
||||||
value = (f'{default_name}() '
|
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}')
|
f'else {f.name}')
|
||||||
else:
|
else:
|
||||||
# This is a field that's not in the __init__ params, but
|
# 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:
|
elif f.default is not MISSING:
|
||||||
# There's a default, this will be the name that's used to look
|
# There's a default, this will be the name that's used to look
|
||||||
# it up.
|
# it up.
|
||||||
default = f'=_dflt_{f.name}'
|
default = f'=__dataclass_dflt_{f.name}__'
|
||||||
elif f.default_factory is not MISSING:
|
elif f.default_factory is not MISSING:
|
||||||
# There's a factory function. Set a marker.
|
# There's a factory function. Set a marker.
|
||||||
default = '=_HAS_DEFAULT_FACTORY'
|
default = '=__dataclass_HAS_DEFAULT_FACTORY__'
|
||||||
return f'{f.name}:_type_{f.name}{default}'
|
return f'{f.name}:__dataclass_type_{f.name}__{default}'
|
||||||
|
|
||||||
|
|
||||||
def _init_fn(fields, std_fields, kw_only_fields, frozen, has_post_init,
|
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} '
|
raise TypeError(f'non-default argument {f.name!r} '
|
||||||
'follows default argument')
|
'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({
|
locals.update({
|
||||||
'MISSING': MISSING,
|
'__dataclass_HAS_DEFAULT_FACTORY__': _HAS_DEFAULT_FACTORY,
|
||||||
'_HAS_DEFAULT_FACTORY': _HAS_DEFAULT_FACTORY,
|
|
||||||
'__dataclass_builtins_object__': object,
|
'__dataclass_builtins_object__': object,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -285,6 +285,23 @@ class TestCase(unittest.TestCase):
|
||||||
c = C(5)
|
c = C(5)
|
||||||
self.assertEqual(c.BUILTINS, 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):
|
def test_field_named_like_builtin(self):
|
||||||
# Attribute names can shadow built-in names
|
# Attribute names can shadow built-in names
|
||||||
# since code generation is used.
|
# since code generation is used.
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Fix issues when defining dataclasses that have fields with specific underscore names that aren't clearly reserved by :mod:`dataclasses`.
|
Loading…
Add table
Add a link
Reference in a new issue