3

Consider the following code:

from typing import Union

class A:
  def function_in_a(self) -> str:
    return 'a'

class B:
  def function_in_b(self) -> str:
    return "b"

class C(A, B):
  pass

def call_functions(c: Union[A, B]) -> None:
  print(c.function_in_a())
  print(c.function_in_b())

if __name__=="__main__":
  c = C()
  call_functions(c)

Note that the function call_functions relies on definitions contained in both classes A and B. It expects objects that inherit from both of these classes.

This code will compile when run using python test.py. But mypy --strict test.py throws an error:

test.py:15: note: Revealed type is "Union[test.A, test.B]"
test.py:16: error: Item "B" of "Union[A, B]" has no attribute "function_in_a"
test.py:17: error: Item "A" of "Union[A, B]" has no attribute "function_in_b"
Found 2 errors in 1 file (checked 1 source file)

This makes sense to me. Union means that c can be a subclass of either A or B, but not both. I saw mention of an Intersection type in PEP483 but a quick perusal of the typing module docs showed that this type was never implemented.

How can I get mypy to recognize that parameters of call_functions are objects which inherit from both A and B using type hinting?

1
  • It's currently not possible, there is an open issue to support it, the suggest workaround is to use Protocol - github.com/python/mypy/issues/8355 Commented Jan 23, 2022 at 8:05

2 Answers 2

2

Use typing.Protocol (New in version 3.8.) to define a type that must implement both methods invoked in the function.

from typing import Protocol


class A:
    def function_in_a(self) -> str:
        return 'a'


class B:
    def function_in_b(self) -> str:
        return "b"


class C(A, B):
    pass


class D(B):
    pass


class ProtoAB(Protocol):
    def function_in_a(self) -> str:
        ...

    def function_in_b(self) -> str:
        ...


def call_functions(obj: ProtoAB) -> None:
    print(obj.function_in_a())
    print(obj.function_in_b())


def main() -> None:

    c = C()
    call_functions(c)
    d = D()
    call_functions(d)


if __name__ == "__main__":
    main()
Sign up to request clarification or add additional context in comments.

2 Comments

This is so much more complicated than the original code. Also, I feel like this is improper use of protocols.... I'm mainly concerned with type hinting a simple case of inheriting from multiple types. There has got to be an easier way.
After digressing into the issue indicated by @daniel and looking a bit more deeply into protocols, I'm convinced this is the correct answer. Intersections are not yet implemented in the typing module, and the fact that Protocols can help alleviate this problem is stated explicitly in PEP544: One can use multiple inheritance to define an intersection of protocols. If this will prove to be a widely used scenario, then a special intersection type construct could be added in future as specified by PEP 483, see rejected ideas for more details.
0

Another solution is to make A and B Protocol. Protocols can can be used as normal class:

from typing import Protocol
        
class A(Protocol):
    def function_in_a(self) -> str:
        return 'a'
class B(Protocol):
    def function_in_b(self) -> str:
        return "b"
class AB(A, B, Protocol):
    pass
    
def call_functions(c: AB) -> None:
    print(c.function_in_a())
    print(c.function_in_b())

class C(A, B):
    pass
call_functions(C())

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.