Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Conditional Python Typing for Generic Functions

Tags:

python

typing

Assuming I have a class A and a derived class B:

from typing import Type, TypeVar

class A:
    type_field: Type[int] = int

class B(A):
    type_field: Type[str] = str

I want to have a function

T = TypeVar('T')

def func(object: T, some_field): 
    ...

A want some_field to be annotated (e.g. to show type hints) with type of T.type_field, assuming T derives from A or A itself. In other words, I want call func(A(), ... to suggest me passing int next, while call func(B(), ... should suggest passing string. Is there any way to achieve it in Python 3?

I want to develop a proper typing for function def func so developers get a proper type hint depending on the type of object passed first.

like image 958
Aleksandr Lobanov Avatar asked Mar 06 '26 19:03

Aleksandr Lobanov


1 Answers

This inheritance hierarchy is broken from the start. All instances of A are supposed to have a type_field attribute of static type type[int], but instances of B are instances of A that do not satisfy this property. mypy will already complain, even before you try to write your func:

from typing import Type, TypeVar

class A:
    type_field: Type[int] = int

class B(A):
    type_field: Type[str] = str

Output:

main.py:7: error: Incompatible types in assignment (expression has type "type[str]", base class "A" defined the type as "type[int]")  [assignment]
Found 1 error in 1 file (checked 1 source file)

That said, you can sort of write a function like what you want, with a potential caveat:

from typing import Protocol, TypeVar

T = TypeVar('T')

class FieldHaver(Protocol[T]):
    type_field: type[T]

def func(a: FieldHaver[T], b: type[T]):
    pass

The caveat is that type is covariant, so this will also accept any subtype of the type that type_field is annotated with. So these calls will work, like you want:

func(A(), int)
func(B(), str)

and this will fail (with a less helpful error message than you'd probably like):

func(B(), int)

but this will pass, which it sounds like you don't want:

func(A(), bool)  # bool is a subclass of int

I don't think there's any way around the covariance... although, if you don't want covariance, there's probably no point taking the second argument in the first place.

like image 93
user2357112 supports Monica Avatar answered Mar 09 '26 08:03

user2357112 supports Monica



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!