Skip to content

Commit 0a8431d

Browse files
committed
[pythongh-119581] Add a test of InitVar with name shadowing
As originally discussed in python/mypy#17219, MyPy has had a false-positive bug report because it errors when a dataclass has methods that shadow an `InitVar` field. It is actually a bit surprising that this works, it turns out that `__annotations__` "remembers" field assignments even if the bound names are later overwritten by methods; it will *not* work to provide a default value. There have been multiple bug reports on MyPy so we know people are actually relying on this in practice; most likely it comes up when a dataclass wants to take a "raw" value as an InitVar and transform it somehow in `__post_init__` into a different value before assigning it to a field; in that case they may choose to make the actual field private and provide a property for access. I currently provide a test of the happy path where there is no default value provided, but no tests of the behavior when no default is provided (in which case the property will override the default) and no documentation (because I'm not sure we want to consider this behavior officially supported). The main goal is to have a regression test since it would be easy for a refactor to break this.
1 parent 0220663 commit 0a8431d

File tree

1 file changed

+23
-0
lines changed

1 file changed

+23
-0
lines changed

Lib/test/test_dataclasses/__init__.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1317,6 +1317,29 @@ def __post_init__(self, init_base, init_derived):
13171317
c = C(10, 11, 50, 51)
13181318
self.assertEqual(vars(c), {'x': 21, 'y': 101})
13191319

1320+
def test_init_var_name_shadowing(self):
1321+
# Because dataclasses rely exclusively on `__annotations__` for
1322+
# handling InitVar and `__annotations__` preserves shadowed definitions,
1323+
# you can actually shadow an InitVar with a method or property.
1324+
#
1325+
# This only works when there is no default value; `dataclasses` uses the
1326+
# actual name (which will be bound to the shadowing method) for default
1327+
# values.
1328+
@dataclass
1329+
class C:
1330+
shadowed: InitVar[int]
1331+
_shadowed: int = field(init=False)
1332+
1333+
def __post_init__(self, shadowed):
1334+
self._shadowed = shadowed
1335+
1336+
@property
1337+
def shadowed(self):
1338+
return self._shadowed
1339+
1340+
c = C(5)
1341+
self.assertEqual(C.shadowed, 5)
1342+
13201343
def test_default_factory(self):
13211344
# Test a factory that returns a new list.
13221345
@dataclass

0 commit comments

Comments
 (0)