I am trying to add a type annotation to a function input argument that is a dataclass
with attributes that overlap with another dataclass
, which actually gets passed in as an input argument.
Consider the following code:
from dataclasses import dataclass
from typing import TypeVar
@dataclass
class Foo:
a: str
zar: str
@dataclass
class Car(Foo):
b: str
@dataclass
class CarInterface:
a: str
b: str
mar = TypeVar("mar", bound=CarInterface)
def blah(x: mar):
print(x.a)
car_instance = Car(a="blah blah", zar="11", b="bb")
blah(car_instance)
In this example, I'm trying to create my own type annotation mar
which is bound by CarInterface
. I want to check that whatever class is passed into blah()
at least has a
and b
attributes (don't care if the class has other attributes such as zar
). I want to do it this way because class Car
(which actually gets passed in) is one of many classes that will be written in the future and passed into this function.
I also want it to be very easy to define a new Car
, so I would like to avoid abstract classes as I don't think the added complexity is worth mypy being happy.
So I'm trying to create mar
which uses duck typing to say that Car
satisfies the interface of CarInterface
.
However, I get two mypy errors.
The first is on the mar
annotation in def blah
TypeVar "mar" appears only once in generic function signaturePylancereportInvalidTypeVarUse
And the other is where I pass car_instance
into blah()
Argument of type "Car" cannot be assigned to parameter "x" of type "bar@blah" in function "blah"
Type "Car" cannot be assigned to type "CarInterface"
"Car" is incompatible with "CarInterface"PylancereportGeneralTypeIssues
CodePudding user response:
Use a Protocol
to define CarInterface
rather than a dataclass
:
from dataclasses import dataclass
from typing import Protocol
@dataclass
class Foo:
a: str
zar: str
@dataclass
class Car(Foo):
b: str
class CarInterface(Protocol):
a: str
b: str
def blah(x: CarInterface):
print(x.a)
car_instance = Car(a="blah blah", zar="11", b="bb")
blah(car_instance)
The above code will typecheck fine, but if you try to pass blah
a Foo
instead of a Car
you'll get a mypy error like this:
test.py:22: error: Argument 1 to "blah" has incompatible type "Foo"; expected "CarInterface"
test.py:22: note: "Foo" is missing following "CarInterface" protocol member:
test.py:22: note: b
Found 1 error in 1 file (checked 1 source file)
A Protocol
can be used as the bound for a TypeVar
, but it's only necessary to use a TypeVar
if you want to indicate that two variables not only implement the protocol but are also the same specific type (e.g. to indicate that a function takes any object implementing CarInterface
and returns the same exact type of object rather than some other arbitrary CarInterface
implementation).