Duck Typing Annotations in Python3

Question:

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
Asked By: Sean

||

Answers:

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

Answered By: Samwise
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.