Does mypy not allow you to define the same variable twice?

Question:

I’ve started using Mypy on my code, and I’ve got the following snippet here:

class BPDataStore():
    def __init__(self, path: pathlib.Path):
        self.path: pathlib.Path = path
        if self.path.exists():
            with self.path.open("r") as F:
                self._bpdata: list[dict] = json.loads(F.read())
        else:
            self._bpdata: list[dict] = {}

Running mypy . from the project root gets me the following errors (there are others, but they’re in other files and I want to focus on this one right now)

bp_trackerbackdata_store.py:13: error: Attribute "_bpdata" already defined on line 11

Why doesn’t mypy allow "redeclaration" like this? It works in production just fine, and doesn’t seem very unreasonable to me. And assuming that there’s no way around this, what’s the correct way to write this code so that it passes mypy’s check?

For now I’ve just removed the annotation from the 2nd _bpdata attribute to get it to pass.

Currently using:

  • Python 3.10.4

  • Mypy 0.982

Asked By: Enrico Tuvera Jr

||

Answers:

The Python interpreter does not care about annotations either way (so long as they are syntactically correct), which is why this "works" as you said.

But defining a variable more than once is generally not type safe. In this case it technically doesn’t matter, because the definitions are identical, but I think mypy just simply disallows re-defining.

If you want to be explicit, you can either define it on the class in advance or inside that method beforehand. In both cases you just don’t assign a value to it:

class BPDataStore:
    _bpdata: list[dict]  # here
    ...

    def __init__(self, path: pathlib.Path):
        self._bpdata: list[dict]  # or here
        ...

But the problem seems to be that your type annotation doesn’t match what you assign it. {} is an empty dict and not a list.

Assuming that was a typo and you are certain that you would always get a list from that json.loads (i.e. the top-level is a JSON array), you could just assign an empty list first and then potentially overwrite it with what you load from the file.

Also, I would suggest including the type arguments for generic types like dict. Here is how I would do it:

import json
from pathlib import Path
from typing import Any

class BPDataStore:
    def __init__(self, path: Path) -> None:
        self.path = path
        self._bpdata: list[dict[str, Any]] = []
        try:
            with self.path.open("r") as f:
                self._bpdata = json.loads(f.read())
        except Exception as e:
            # handle exception `e`...

Notice also that I don’t explicitly annotate self.path because it would be inferred by any type checker based on that first assignment via the typed argument path. But at that point it is just a matter of preference.

EDIT: Thanks to @SUTerliakov for pointing out that you should indeed wrap your file opening in a try-block, instead of checking for existence. I edited my code example accordingly. If you are only worried the file may not exist, you should catch FileNotFoundError.

Answered By: Daniil Fajnberg
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.