Here is a (slightly contrived) example where I read some data from an external source, check that the data is of a type I expect, and then try to use an attribute of that specific data type:
from typing import Type, Union
def receive() -> bytes:
...
DTYPE_BYTE = 0
# DataType, VersionData, ChecksumData omitted for brevity.
PARSE_MAP: dict[int, Union[Type[VersionData], Type[ChecksumData]]] = {
DataType.VERSION: VersionData,
DataType.CHECKSUM: ChecksumData,
}
def read(expected: DataType) -> Union[VersionData, ChecksumData]:
raw = receive()
data = PARSE_MAP[raw[DTYPE_BYTE]](raw)
if data.dtype == expected:
return data
raise RuntimeError
read(DataType.VERSION).version # mypy error: Item "ChecksumData" of "Union[VersionData, ChecksumData]" has no attribute "version".
Since the read
function can return types which do not have the version
attribute, mypy does not like this. Mypy’s documentation suggests using assert
to make the error go away:
data = read(DataType.VERSION)
assert isinstance(data, VersionData)
data.version
However, since I have already checked that the data does have this attribute, I’m wondering if there is any advantage to the above over just
read(DataType.VERSION).version # type: ignore
?
The latter is less verbose and clearer (in my opinion, it’s not obvious what the assert
is doing unless one is already familiar with mypy). So,
- Is type narrowing with assert a code smell in this example?
- Is type narrowing with assert always a code smell?