Store and assert type in a Python class

Viewed 1000

PyCharm CE 2019.2.3 warns me about a class in that I store a list of data along with the value data type. I have tried to reduce it to the essence:

class Data:
    def __init__(self, length, data_type):
        assert isinstance(length, int) and length >= 0
        assert isinstance(data_type, type)  # commenting this line out removes the warning
        self._data = [None] * length
        self._data_type = data_type

    def set_field(self, index, value):
        assert isinstance(value, self._data_type)  # commenting this line out removes the warning
        assert isinstance(index, int)
        self._data[index] = value

I get a warning on the index:

enter image description here

Unexpected type(s):
(int, type)

Possible types:
(int, None)
(slice, Iterable[None])

Inspection Info: This inspection detects type errors in function call expressions. Due to dynamic dispatch and duck typing, this is possible in a limited but useful number of cases. Types of function parameters can be specified in docstrings or in Python 3 function annotation.

Further comments:

  • removing at least one of the two marked assertions removes the warning

  • it is rather confusing that the warning appears for the index while those asserts refer to the value

  • adding additional asserts or type-hinting docstrings did not remove the warning for me

My question: Am I doing something wrong with this storing and asserting of types? Is there any reason for that warning, and if not, do you see a convenient way to remove it?

1 Answers

The problem here is two-fold:

  • PyCharm has a bit of a blind spot (at the time of writing) with regards to handling metaclasses correctly with typing. So without additional "hints" it cannot deduce the correct type for value currently.
  • Because you initialized your list with None PyCharm treats it as a list containing Nones.

Let's start with the metaclass problem:

You check if data_type is a class by checking if it's an instance of type (the default metaclass of classes). Then you check if the value is an instance of the class. That's fine.

However PyCharm assumes that your data_type is a type (which is correct) but after the isinstance(value, self._data_type) it also assumes that value is a type (which is incorrect - it should be of type _data_type). So just by using assert isinstance(...) with data_type and value PyCharm won't be able to deduce the correct type of value!

So this probably qualifies as Bug or missing feature in PyCharm - or in whatever library PyCharm uses to determine the types.


The second issue is that by initializing the _data with None PyCharm will infer the type of _data to be List[None] (a list containing Nones).

So if PyCharm deduces anything except Any (which can be assigned to anything) or None (which would be the expected type for the list content) for value it will result in a warning.

Even with:

    def set_field(self, index, value):
        assert isinstance(value, int)  # <-- difference
        assert isinstance(index, int)
        self._data[index] = value

The warning will come.


At this point you have two options:

  • Ignore the warning.
  • Use full-fledged type-hinting (if the targeted Python version(s) allow for it).

If you want to use type-hinting you could for example use:

from typing import List, Optional, TypeVar, Generic, Type

T = TypeVar('T')

class Data(Generic[T]):

    _data: List[Optional[T]]
    _data_type: Type[T]

    def __init__(self, length: int, data_type: Type[T]):
        self._data = [None] * length
        self._data_type = data_type

    def set_field(self, index: int, value: T):
        if isinstance(value, self._data_type):
            self._data[index] = value
        raise TypeError()

Note: I've removed the asserts completely. If arguments don't have the expected type or value a TypeError or ValueError would be more informative. However in most cases documentation and/or type-hints can adequately replace asserts and also TypeErrors in Python (at least with an IDE or when using mypy).

Related