Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

mypy type checking on Callable thinks that member variable is a method

Tags:

python

mypy

When I run mypy over the following code I see several errors:

from typing import Callable, Type


def class_creator(outside_reference: Callable[[str], None]) -> Type[object]:
    class SomeClass():
        reference: Callable[[str], None]

        def __init__(self) -> None:
            self.reference = outside_reference
            super().__init__()

        def __str__(self):
            self.reference("SomeClass instance")

    return SomeClass


def callback(string: str) -> None:
    print("Prepping: " + string)


instance = class_creator(callback)()
print(instance)

Here are the errors:

test.py:9: error: Cannot assign to a method
test.py:9: error: Invalid self argument "SomeClass" to attribute function "reference" with type "Callable[[str], None]"
test.py:9: error: Incompatible types in assignment (expression has type "Callable[[str], None]", variable has type "Callable[[], None]")

Line #9 is self.reference = outside_reference.

I'm basically positive that I'm just misunderstanding something, but I just can't see where I'm going wrong.

This is the minimal reproducible reference. If I change the types from Callable[[str], None] to int (and don't actually call it), then it runs just fine without showing any errors. It's only when I switch to Callable that it starts showing these errors.

What should my annotations be here?

like image 431
Dale Myers Avatar asked Dec 24 '22 05:12

Dale Myers


2 Answers

Until the issue in https://github.com/python/mypy/issues/708 is fixed, one clean way to work around this is to make the callable attribute optional, and wrap it in a method with an assert:

from typing import Any, Callable, Optional
class SomeClass:
  _reference: Optional[Callable[[], Any]]

  def reference(self) -> Any:
    assert self._reference is not None
    return self._reference()

  def __init__(self, reference):
    self.reference = reference

c = SomeClass(lambda: 42)
print(c.reference())
$ mypy test.py
Success: no issues found in 1 source file
like image 121
jakevdp Avatar answered Apr 08 '23 08:04

jakevdp


A similar but shorter workaround is to annotate the member with a Union type, duplicating the Callable type:

from typing import Callable, Union
class SomeClass:
  reference: Union[Callable[[], int], Callable[[], int]]

  def __init__(self, reference: Callable[[], int]):
    self.reference = reference

c = SomeClass(lambda: 42)
print(c.reference())
like image 41
jkinkead Avatar answered Apr 08 '23 06:04

jkinkead