I'm trying to do some typing on my python code, and I got the following mypy error: "Unsupported target for indexed assignment"
On a simplified example, it amounts to the following code:
from pathlib import Path
from typing import (Literal, Mapping,
Optional, Union)
STRAND = Literal["+", "-"]
PATH = Union[str, Path]
fastq_files: Mapping[STRAND, Optional[PATH]] = { # simultaneous annotation and assignment
"+": None,
"-": None}
reads_dir = Path("/tmp")
fastq_files["+"] = reads_dir.joinpath( # mypy error
"plus.fastq.gz")
fastq_files["-"] = reads_dir.joinpath( # mypy error
"minus.fastq.gz")
The error comes when replacing None
with a Path
in the dictionaries values.
How come values supposed to be of type Optional[PATH]
cannot be replaced with values of type Path
, given that PATH
is Union[str, Path]
?
I would have thought that a Path
is compatible with Union[str, Path]
, which in turn is compatible with Optional[Union[str, Path]]
.
And why does the error disappear when I annotate the dict before assignment instead of annotating it at assignment time (see below)?
from pathlib import Path
from typing import (Literal, Mapping,
Optional, Union)
STRAND = Literal["+", "-"]
PATH = Union[str, Path]
fastq_files: Mapping[STRAND, Optional[PATH]] # annotation before assignment
fastq_files = {
"+": None,
"-": None}
reads_dir = Path("/tmp")
fastq_files["+"] = reads_dir.joinpath( # no mypy error
"plus.fastq.gz")
fastq_files["-"] = reads_dir.joinpath( # no mypy error
"minus.fastq.gz")
The above shows that a None
can be replaced by a Path
in a "slot" with type Optional[Union[str, Path]]
.
Does it mean that when I do the annotation at the same time as the assignment, the actual type is "reduced" to the strictest possible type compatible with the assigned value? (with the consequence that the "slot" gets a more restrictive type)
The problem is that Mapping is supposed to be a read-only protocol -- if you check the type hints for Mapping, you can see it literally does not define a __setitem__
method.
If you want to be able to mutate your mapping, you'll need to use Dict or MutableMapping instead.
Switching to using a TypedDict as suggested by the other answer would also work, since TypedDicts are assumed to be a subtype of Dict and so are inherently mutable.
I think TypedDict
solves this (weird) problem:
from pathlib import Path
from typing import (Literal, Mapping,
Optional, Union, TypedDict)
STRAND = Literal["+", "-"]
PATH = Union[str, Path]
FASTQ = TypedDict("FASTQ", {"+": Optional[PATH], "-": Optional[PATH]})
fastq_files: FASTQ = {
"+": None,
"-": None}
reads_dir = Path("/tmp")
fastq_files["+"] = reads_dir.joinpath("plus.fastq.gz")
fastq_files["-"] = reads_dir.joinpath("minus.fastq.gz")
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With