pythonpython-typingmypy

Understanding unbound type error with mypy


I am new to static type checking in python and honestly I believed it was much easier that what it actually is.

Here is an ultra simplified version of my code:


from typing import Collection, TypeVar, Generic

ItemType = TypeVar('ItemType')

class WorkerMeta(type):
    pass

class Worker(metaclass=WorkerMeta):

    def __init__(self) -> None:

        self.item: ItemType  # error: Type variable "unbound.ItemType" is unbound  [valid-type]
                             # (Hint: Use "Generic[ItemType]" or "Protocol[ItemType]" base class 
                             # to bind "ItemType" inside a class)
                             # (Hint: Use "ItemType" in function signature to bind "ItemType" 
                             # inside a function)
                  

    def get_collection(self) -> Collection[ItemType]:
        l : Collection[ItemType] = []
        return l

    def run(self) -> None:
        items: Collection[ItemType] = self.get_collection() # error: Type variable "unbound.ItemType" is unbound  [valid-type]
                             # (Hint: Use "Generic[ItemType]" or "Protocol[ItemType]" base class 
                             # to bind "ItemType" inside a class)
                             # (Hint: Use "ItemType" in function signature to bind "ItemType" 
                             # inside a function)
        for self.item in items:
            print(self.item) 

class MyWorker(Worker):

    def get_collection(self) -> Collection[ItemType]:
        return [1,2,3]  # error: List item 0 has incompatible type "int"; expected "ItemType"  [list-item]
                        # error: List item 1 has incompatible type "int"; expected "ItemType"  [list-item]
                        # error: List item 2 has incompatible type "int"; expected "ItemType"  [list-item]

w = MyWorker()
w.run()
# output will be
# 1
# 2
# 3

The worker class is defining a kind of execution scheme that is then reproduced by all its subclasses.

In the run method there is a loop on a collection made of elements all of the same type, but that might be different in different worker subclasses. I thought this was the task for a TypeVar.

The code works perfectly. But mypy is complaining a lot. I put the error messages as comment in the code.

Can you suggest me how to fix this issue with typing keeping my code still working?


Solution

  • At the very least, your worker class must be generic in the item type in order for ItemType to be bound to a specific class at runtime. Something like

    from typing import Generic, TypeVar
    
    
    ItemType = TypeVar('ItemType')
    
    
    class Worker(Generic[ItemType]):
        item: ItemType
    
    
        def __init__(self) -> None:
            ...
                      
    
        def get_collection(self) -> Collection[ItemType]:
            l : Collection[ItemType] = []
            return l
    
        def run(self) -> None:
            items: Collection[ItemType] = self.get_collection()
            for self.item in items:
                print(self.item) 
    

    Usually, the call to __init__ will fix the value of ItemType for a particular instance of Worker: maybe it takes one or more values of type ItemType, or a list of type list[ItemType] to use as the initial collection. But you can also fix the type "manually", with something like

    w: Worker[int] = Worker()