Search code examples
pythonpython-typingmypy

How can I satisfy mypy when I have a potential callable that involves Self?


I have a dataclass that has a field that might be a constant or might be a function taking Self. There's a helper function that just does the right thing -- if the field contains a constant, it returns the constant. If the field contains a function, it calls the function using self:

from dataclasses import dataclass
from typing import Self, Callable

@dataclass
class MyClass:
    my_func_field: str | Callable[[Self], str]
    
    def my_field(self) -> str:
        if isinstance(self.my_func_field, str):
            return self.my_func_field
        else:
            return self.my_func_field(self)

mypy doesn't like this (playground):

main.py:12: error: Argument 1 has incompatible type "MyClass"; expected "Self"  [arg-type]

However, if I simplify the situation so it's just a callable, I don't run into this problem:

from dataclasses import dataclass
from typing import Self, Callable

@dataclass
class MyClass:
    my_func_field: Callable[[Self], str]
    
    def my_field(self) -> str:
        return self.my_func_field(self)

This is surprising to me. I assumed that the else branch in the first example would type-check exactly the same as the second example, because in the else branch, my_func_field has been narrowed to Callable[[Self], str], which is exactly the same type as the second example.

What am I missing here? Is it possible to get mypy to accept something, or do I have to use an # ignore comment?


Solution

  • Here's at least one workaround using string types instead of Self.

    from dataclasses import dataclass
    from typing import Self, Callable
    
    @dataclass
    class MyClass:
        my_func_field: str | Callable[["MyClass"], str]
        
        def my_field(self) -> str:
            if isinstance(self.my_func_field, str):
                return self.my_func_field
            else:
                return self.my_func_field(self)