Search code examples
pythonmypytyping

Python type casting when preallocating list


This question might already have an answer, so please guide me to one if you know any. I couldn't find one myself, though this question feels like a common one.

So, consider the following pattern:

arr = [None] * n
for i in range(n):
    # do some computations
    # ...
    # even more computations
    arr[i] = MyClass(some_computed_value)

So far so good, I tend to use this pattern from time to time. Now, let us be thorough in our attempt to provide all the code with type annotations. The problem is that we preallocate our array with Nones, so it has the type list[None]. But we want it to be list[MyClass]. How do we proceed?

The most straightforward solution is making it optional:

arr: list[Optional[MyClass]] = [None] * n

This solves the type checker issue, but now it's our issue since that Optional prohibits us from performing even basic operations on the result

arr[0].my_method()
# error: NoneType has no attribute "my_method"

Long story short, I end up with the following pattern:

arr_: Any = [None] * n
for i in range(n):
    # ...
    arr_[i] = MyClass(some_computed_value)
arr = typing.cast(list[MyClass], arr_)

This is ugly, inconvenient, barely readable and boilerplate. What do you do?


Solution

  • You can lie to your type checker when you first initialize arr to "trick" it that arr never contains None entries. For the purposes of static type checking, arr will be a list[MyClass], even though it briefly contains None entries at runtime. You of course assume responsibility for making sure that this assumption plays out.

    For example,

    from typing import cast, reveal_type
    
    n = 1000
    
    arr: list[int] = [cast(int, None)] * n
    # or alternatively
    arr: list[int] = [None] * n # type: ignore
    
    for i in range(n):
        arr[i] = i
    
    reveal_type(arr)
    reveal_type(arr[0])
    

    passes type checking with both mypy and pyright, and outputs

    # mypy
    tmp.py:9: note: Revealed type is "builtins.list[builtins.int]"
    tmp.py:10: note: Revealed type is "builtins.int"
    Success: no issues found in 1 source file
    
    # pyright
    file.py
      file.py:9:13 - information: Type of "arr" is "list[int]"
      file.py:10:13 - information: Type of "arr[0]" is "int"
    0 errors, 0 warnings, 2 informations