how do I avoid "not subscriptable" type checks from Pylint when I later set a variable value?

Viewed 725

I have some python code I'm trying to type hint PEP484 style, and getting spurious warnings.

Going through a loop, I first initialize a last_row to None. on the first pass I give it a value, and then next time I do something with it. However, Pylint still thinks it's None even though I declared the type as a Dict

How to avoid best this?

I thought of putting some value other than None on initialize just to shut up the linter, but this seems like a hack.

last_row: Dict = {}

Maybe the linter is pointing to a code smell and I should fix this to have a more consistent type/value for the last_row ...

        last_row: Dict = None

        for index, row in self.df.iterrows():
            if last_row is not None:
                row['intent_from'] = str(last_row['intent'])
            last_row = row

in the code above I get this error:

Value 'last_row' is unsubscriptable

I believe the typescript checker is smarter about these kind of things, but it is making a runtime guess so that also feels a bit fragile.

3 Answers

Answer for python 3.9

As of python 3.9, typing.Dict is deprecated. The reason for deprecating typing.Dict is that classes now support brackets for type annotation. Now, built in classes are recognized as generics.

from mypy Documentation:

# For mappings, we need the types of both keys and values
x: dict[str, float] = {'field': 2.0}  # Python 3.9+
x: Dict[str, float] = {'field': 2.0}

With the second example being deprecated from python 3.9

One option is to define the type hint as a Union of Dict and None, however, that would have a different problem. It doesn't assure that it's subscriptable.

The problem is that None is not of type dictionary, so last_row: Dict = None is incorrect.

A solution for this could be initializing the dictionary as an empty dictionary and then checking if the key exists before accessing it.

last_row: Dict = {}

for index, row in self.df.iterrows():
    if 'intent' in last_row:
        row['intent_from'] = str(last_row['intent'])
    last_row = row

Python mypy checker supports argument strict_optional which when set in mypy.ini as strict_optional=False will treat Optional[Dict] as Dict. By default it is set to True. Also the same function is achievable by passing --no-strict-optional as command line flag.

Example:

d: dict[str, str] = None

Default mypy output

error: Incompatible types in assignment (expression has type "None",
variable has type "Dict[str, str]")
    d: dict[str, str] = None

With --no-strict-optional

Mypy output

$mypy --no-strict-optional filename.py
Success: no issues found in 1 source file
Related