Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Unsupported target for indexed assignment" with mypy, depending on type hinting moment with respect to assignment

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)

like image 358
bli Avatar asked Feb 28 '20 16:02

bli


2 Answers

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.

like image 137
Michael0x2a Avatar answered Nov 17 '22 02:11

Michael0x2a


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")
like image 1
po5i Avatar answered Nov 17 '22 03:11

po5i