bpo-32960: For dataclasses, disallow inheriting frozen from non-frozen classes and vice-versa, (GH-5919)
This restriction will be relaxed at a future date.
This commit is contained in:
parent
72d9b2be36
commit
2fa6b9eae0
|
@ -623,14 +623,21 @@ def _process_class(cls, repr, eq, order, unsafe_hash, init, frozen):
|
||||||
else:
|
else:
|
||||||
setattr(cls, f.name, f.default)
|
setattr(cls, f.name, f.default)
|
||||||
|
|
||||||
|
# We're inheriting from a frozen dataclass, but we're not frozen.
|
||||||
|
if cls.__setattr__ is _frozen_setattr and not frozen:
|
||||||
|
raise TypeError('cannot inherit non-frozen dataclass from a '
|
||||||
|
'frozen one')
|
||||||
|
|
||||||
|
# We're inheriting from a non-frozen dataclass, but we're frozen.
|
||||||
|
if (hasattr(cls, _MARKER) and cls.__setattr__ is not _frozen_setattr
|
||||||
|
and frozen):
|
||||||
|
raise TypeError('cannot inherit frozen dataclass from a '
|
||||||
|
'non-frozen one')
|
||||||
|
|
||||||
# Remember all of the fields on our class (including bases). This
|
# Remember all of the fields on our class (including bases). This
|
||||||
# marks this class as being a dataclass.
|
# marks this class as being a dataclass.
|
||||||
setattr(cls, _MARKER, fields)
|
setattr(cls, _MARKER, fields)
|
||||||
|
|
||||||
# We also need to check if a parent class is frozen: frozen has to
|
|
||||||
# be inherited down.
|
|
||||||
is_frozen = frozen or cls.__setattr__ is _frozen_setattr
|
|
||||||
|
|
||||||
# Was this class defined with an explicit __hash__? Note that if
|
# Was this class defined with an explicit __hash__? Note that if
|
||||||
# __eq__ is defined in this class, then python will automatically
|
# __eq__ is defined in this class, then python will automatically
|
||||||
# set __hash__ to None. This is a heuristic, as it's possible
|
# set __hash__ to None. This is a heuristic, as it's possible
|
||||||
|
@ -654,7 +661,7 @@ def _process_class(cls, repr, eq, order, unsafe_hash, init, frozen):
|
||||||
if f._field_type in (_FIELD, _FIELD_INITVAR)]
|
if f._field_type in (_FIELD, _FIELD_INITVAR)]
|
||||||
_set_new_attribute(cls, '__init__',
|
_set_new_attribute(cls, '__init__',
|
||||||
_init_fn(flds,
|
_init_fn(flds,
|
||||||
is_frozen,
|
frozen,
|
||||||
has_post_init,
|
has_post_init,
|
||||||
# The name to use for the "self" param
|
# The name to use for the "self" param
|
||||||
# in __init__. Use "self" if possible.
|
# in __init__. Use "self" if possible.
|
||||||
|
@ -696,7 +703,7 @@ def _process_class(cls, repr, eq, order, unsafe_hash, init, frozen):
|
||||||
f'in class {cls.__name__}. Consider using '
|
f'in class {cls.__name__}. Consider using '
|
||||||
'functools.total_ordering')
|
'functools.total_ordering')
|
||||||
|
|
||||||
if is_frozen:
|
if frozen:
|
||||||
for name, fn in [('__setattr__', _frozen_setattr),
|
for name, fn in [('__setattr__', _frozen_setattr),
|
||||||
('__delattr__', _frozen_delattr)]:
|
('__delattr__', _frozen_delattr)]:
|
||||||
if _set_new_attribute(cls, name, fn):
|
if _set_new_attribute(cls, name, fn):
|
||||||
|
|
|
@ -637,29 +637,6 @@ class TestCase(unittest.TestCase):
|
||||||
y: int
|
y: int
|
||||||
self.assertNotEqual(Point(1, 3), C(1, 3))
|
self.assertNotEqual(Point(1, 3), C(1, 3))
|
||||||
|
|
||||||
def test_frozen(self):
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class C:
|
|
||||||
i: int
|
|
||||||
|
|
||||||
c = C(10)
|
|
||||||
self.assertEqual(c.i, 10)
|
|
||||||
with self.assertRaises(FrozenInstanceError):
|
|
||||||
c.i = 5
|
|
||||||
self.assertEqual(c.i, 10)
|
|
||||||
|
|
||||||
# Check that a derived class is still frozen, even if not
|
|
||||||
# marked so.
|
|
||||||
@dataclass
|
|
||||||
class D(C):
|
|
||||||
pass
|
|
||||||
|
|
||||||
d = D(20)
|
|
||||||
self.assertEqual(d.i, 20)
|
|
||||||
with self.assertRaises(FrozenInstanceError):
|
|
||||||
d.i = 5
|
|
||||||
self.assertEqual(d.i, 20)
|
|
||||||
|
|
||||||
def test_not_tuple(self):
|
def test_not_tuple(self):
|
||||||
# Test that some of the problems with namedtuple don't happen
|
# Test that some of the problems with namedtuple don't happen
|
||||||
# here.
|
# here.
|
||||||
|
@ -2475,5 +2452,66 @@ class TestHash(unittest.TestCase):
|
||||||
assert False, f'unknown value for expected={expected!r}'
|
assert False, f'unknown value for expected={expected!r}'
|
||||||
|
|
||||||
|
|
||||||
|
class TestFrozen(unittest.TestCase):
|
||||||
|
def test_frozen(self):
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class C:
|
||||||
|
i: int
|
||||||
|
|
||||||
|
c = C(10)
|
||||||
|
self.assertEqual(c.i, 10)
|
||||||
|
with self.assertRaises(FrozenInstanceError):
|
||||||
|
c.i = 5
|
||||||
|
self.assertEqual(c.i, 10)
|
||||||
|
|
||||||
|
def test_inherit(self):
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class C:
|
||||||
|
i: int
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class D(C):
|
||||||
|
j: int
|
||||||
|
|
||||||
|
d = D(0, 10)
|
||||||
|
with self.assertRaises(FrozenInstanceError):
|
||||||
|
d.i = 5
|
||||||
|
self.assertEqual(d.i, 0)
|
||||||
|
|
||||||
|
def test_inherit_from_nonfrozen_from_frozen(self):
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class C:
|
||||||
|
i: int
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
'cannot inherit non-frozen dataclass from a frozen one'):
|
||||||
|
@dataclass
|
||||||
|
class D(C):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_inherit_from_frozen_from_nonfrozen(self):
|
||||||
|
@dataclass
|
||||||
|
class C:
|
||||||
|
i: int
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
'cannot inherit frozen dataclass from a non-frozen one'):
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class D(C):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_inherit_from_normal_class(self):
|
||||||
|
class C:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class D(C):
|
||||||
|
i: int
|
||||||
|
|
||||||
|
d = D(10)
|
||||||
|
with self.assertRaises(FrozenInstanceError):
|
||||||
|
d.i = 5
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
For dataclasses, disallow inheriting frozen from non-frozen classes, and
|
||||||
|
also disallow inheriting non-frozen from frozen classes. This restriction
|
||||||
|
will be relaxed at a future date.
|
Loading…
Reference in New Issue