Multiple inheritance using typing module and mypy - python

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?

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()

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())

Related

Why does mypy throw "Cannot instantiate abstract class" when it is not being instantiated

I have some code that, when run, runs successfully however mypy complains about it by throwing an error. Below is a simple contrived example that demonstrates the error:
from typing import List
from abc import ABC, abstractmethod
class Abc_(ABC):
def __init__(self) -> None:
pass
#abstractmethod
def f(self) -> str:
return "Abc"
class A(Abc_):
def f(self) -> str:
return "a"
class B(Abc_):
def f(self) -> str:
return "b"
class C(Abc_):
def f(self) -> str:
return "c"
class DynamicClassInsantiator(object):
CLASSES = [A, B, C]
# CLASSES = [A, B]
# CLASSES = [A, C]
# CLASSES = [B, C]
#property
def stuff(self) -> List[str]:
return [cls().f() for cls in self.CLASSES]
print(DynamicClassInsantiator().stuff)
If I run the file (python path/to/file.py) then it runs successfully and outputs:
['a', 'b', 'c']
Running mypy upon that file returns:
error: Cannot instantiate abstract class "Abc_" with abstract attribute "f" [abstract]
Found 1 error in 1 file (checked 1 source file)
If I comment out CLASSES = [A, B, C] and replace it with any of the lines below it that are currently commented out then mypy no longer throws an error.
Success: no issues found in 1 source file
Here is my python & mypy version
➜ python --version
Python 3.10.4
➜ mypy --version
mypy 0.991 (compiled: yes)
Why does mypy complain about this code? I am not instantiating Abc_ (not intentionally, anyway)
The issue is actually with the class variable CLASSES. If you add a type annotation MyPy should be appeased. Credit to
this post

Why does mypy throw "Cannot instantiate abstract class"? [duplicate]

I have some code that, when run, runs successfully however mypy complains about it by throwing an error. Below is a simple contrived example that demonstrates the error:
from typing import List
from abc import ABC, abstractmethod
class Abc_(ABC):
def __init__(self) -> None:
pass
#abstractmethod
def f(self) -> str:
return "Abc"
class A(Abc_):
def f(self) -> str:
return "a"
class B(Abc_):
def f(self) -> str:
return "b"
class C(Abc_):
def f(self) -> str:
return "c"
class DynamicClassInsantiator(object):
CLASSES = [A, B, C]
# CLASSES = [A, B]
# CLASSES = [A, C]
# CLASSES = [B, C]
#property
def stuff(self) -> List[str]:
return [cls().f() for cls in self.CLASSES]
print(DynamicClassInsantiator().stuff)
If I run the file (python path/to/file.py) then it runs successfully and outputs:
['a', 'b', 'c']
Running mypy upon that file returns:
error: Cannot instantiate abstract class "Abc_" with abstract attribute "f" [abstract]
Found 1 error in 1 file (checked 1 source file)
If I comment out CLASSES = [A, B, C] and replace it with any of the lines below it that are currently commented out then mypy no longer throws an error.
Success: no issues found in 1 source file
Here is my python & mypy version
➜ python --version
Python 3.10.4
➜ mypy --version
mypy 0.991 (compiled: yes)
Why does mypy complain about this code? I am not instantiating Abc_ (not intentionally, anyway)
The issue is actually with the class variable CLASSES. If you add a type annotation MyPy should be appeased. Credit to
this post

Python typing signature for instance of subclass?

Consider:
from __future__ import annotations
class A:
#classmethod
def get(cls) -> A:
return cls()
class B(A):
pass
def func() -> B: # Line 12
return B.get()
Running mypy on this we get:
$ mypy test.py
test.py:12: error: Incompatible return value type (got "A", expected "B")
Found 1 error in 1 file (checked 1 source file)
Additionally, I have checked to see if old-style recursive annotations work. That is:
# from __future__ import annotations
class A:
#classmethod
def get(cls) -> "A":
# ...
...to no avail.
Of course one could do:
from typing import cast
def func() -> B: # Line 12
return cast(B, B.get())
Every time this case pops up. But I would like to avoid doing that.
How should one go about typing this?
The cls and self parameters are usually inferred by mpyp to avoid a lot of redundant code, but when required they can be specified explicitly by annotations.
In this case the explicit type for the class method would look like the following:
class A:
#classmethod
def get(cls: Type[A]) -> A:
return cls()
So what we really need here is a way to make Type[A] a generic parameter, such that when the class method is called from a child class, you can reference the child class instead. Luckily, we have TypeVar values for this.
Working this into your existing example we will get the following:
from __future__ import annotations
from typing import TypeVar, Type
T = TypeVar('T')
class A:
#classmethod
def get(cls: Type[T]) -> T:
return cls()
class B(A):
pass
def func() -> B:
return B.get()
Now mypy should be your friend again! 😎

How to get Mypy to recognize a class's protocol membership within a Callable?

Mypy properly recognizes a class's adherence to a protocol when the protocol is used as a simple parameter to a type-annotated function. However, when I have a function requiring a callable parameter using that protocol, Mypy misses the user class's protocol membership.
Am I misusing Mypy's protocol pattern, or is this something simply not supported by Mypy at the moment?
(I have seen the thread about Mypy having trouble with Callables that get assigned to a class.. so this may be a known behavior)
from typing_extensions import Protocol
from typing import Callable
class P(Protocol) :
def foo(self) -> None : ...
def requires_P(protocol_member : P) -> None :
protocol_member.foo()
def requires_P_callable(protocol_member : P, function: Callable[[P],None]) -> None :
function(protocol_member)
class C :
def foo(self) :
print("bar")
if __name__ == '__main__' :
c = C()
def call_foo(c: C) -> None:
c.foo()
requires_P(c)
# mypy is fine with this
requires_P_callable(c, call_foo)
# mypy complains :
# Argument 2 to "requires_P_callable" has incompatible type "Callable[[C], None]"; expected "Callable[[P], None]"
If replace definition of call_foo with:
def call_foo(c: P) -> None:
c.foo()
error disappears and program continue to work... The situation is the same if stop using Protocol and make C a child of P.
Second workaround is:
from typing import Callable, Protocol, TypeVar
_TP = TypeVar('_TP', bound='P')
class P(Protocol):
def foo(self) -> None:
...
class C:
def foo(self) -> None:
print("foo")
def requires_P_callable(prot: _TP, func: Callable[[_TP], None]) -> None:
func(prot)
def call_foo(c: C) -> None:
c.foo()
if __name__ == '__main__':
c = C()
requires_P_callable(c, call_foo)

`TypeVar`: parametric type as a value for `bound` parameter

I'd like to implement a generic class like this:
S = TypeVar("S")
T = TypeVar("T", bound=OtherParametrizedClass)
class MyClass(Generic[T[S]]):
def some_method(param: S) -> None:
pass
I've already tried the following:
S = TypeVar("S")
T = TypeVar("T", bound=OtherParametrizedClass)
class MyClass(Generic[S, T[S]]):
def some_method(param: S) -> None:
pass
def other_method(param: T) -> None:
pass
It works as expected with MyPy. However, when Python interpreter runs this code, it gives me the following error:
TypeError: 'TypeVar' object is not subscriptable.
As I found, this means that TypeVar has no [] operator implemented.
Does anyone have an idea on how to obtain the solution satisfying both mypy and Python interpreter?
EDIT:
I have also tried the following:
S = TypeVar("S")
T = TypeVar("T", bound=OtherParametrizedClass[S])
class MyClass(Generic[T]):
def some_method(param: S) -> None:
pass
def other_method(param: T) -> None:
pass
Python interpreter doesn't give any errors/warnings. However mypy is complaining about the second line:
Invalid type "S"
I'm not sure I understand exactly what you're trying to achieve.
There are basically two questions:
why do you need to define T?
why is MyClass Generic[T] instead of Generic[S]?
The second question is key: I think fundamentally what you're getting wrong is that you're trying to make MyClass Generic[T], when it should simply be Generic[S], at which point you don't even need to define T.
other_method can just return OtherParametrizedClass[S].
Below an example that I think does what you're trying to achieve:
import dataclasses
from typing import Generic, TypeVar
N = TypeVar("N", int, float)
#dataclasses.dataclass
class Adder(Generic[N]):
to_add: N
def add(self, value: N) -> N:
return value + self.to_add
class Foo(Generic[N]):
def get_adder(self, to_add: N) -> Adder[N]:
return Adder(to_add)
Name mapping from my example to yours:
N is S
Adder is OtherParametrizedClass
Foo is MyClass
Foo.get_adder is MyClass.other_method

Categories