[Python] How Can I Type a Method That Accepts Any Subclass of a Base Class?

Discussão em 'Python' iniciado por Stack, Abril 12, 2025.

  1. Stack

    Stack Membro Participativo

    I'm having some trouble typing subclasses of a base class.

    I have a base class called Table, which defines shared behavior for all table subclasses. Then, I have a Database class that manages collections of these tables.

    The code below works fine at runtime. However, when I try to type the methods in the Database class using Table as the type annotation, I run into issues when passing a subclass (e.g., Ducks). The types break, and MyPy complains.

    I think I need to use a bounded generic or a protocol, but I couldn't figure out how to make it work properly.

    Here’s the relevant code:

    #!/usr/bin/env python3
    from collections.abc import Callable, Iterable, Mapping, Sequence
    from dataclasses import dataclass
    from typing import Any, Self, TypeVar

    # _Table = TypeVar("_Table", bound="Table") <- Tried this but did not work

    @dataclass
    class Table:
    @classmethod
    def get_name(cls) -> str:
    return cls.__name__.lower()


    @dataclass
    class Database:
    records: Mapping[str, Sequence[Table]]

    def filter(
    self, table: type[Table], _filter: Callable[[Table], bool]
    ) -> Iterable[Table]:
    yield from (i for i in self.records[table.get_name()] if _filter(i))
    # USING cast(T) here kind of works but i think is kind a cheat.

    @dataclass
    class Ducks(Table):
    name: str
    age: int


    if __name__ == "__main__":

    records = {
    "ducks": [
    Ducks(name="Patinhas", age=104),
    Ducks(name="Donald", age=30),
    Ducks(name="Huguinho", age=12),
    Ducks(name="Zezinho", age=12),
    Ducks(name="Luizinho", age=12),
    ]
    }

    db = Database(records)
    f: Callable[[Ducks], bool] = lambda t: t.age < 100 # <- BONUS QUESTION: is there a way to not use this long typing lambda and just use the untyped notation ?
    print(*db.filter(Ducks, _filter=f), sep="\n") # <- PROBLEM HERE


    $ python3 ex.py
    Ducks(name='Donald', age=30)
    Ducks(name='Huguinho', age=12)
    Ducks(name='Zezinho', age=12)
    Ducks(name='Luizinho', age=12)


    Mypy lint: ex.py 46 37 error arg-type Argument "_filter" to "filter" of "Database" has incompatible type "Callable[[Ducks], bool]"; expected "Callable[[Table], bool]" (lsp))
    ​

    This makes sense because Callable[[Table], bool] doesn't guarantee compatibility with Ducks, even though it's a subclass. I want the method to work with any subclass of Table and have the type checker understand that. I need a way to tell the type checker that the Database functions should work with any subclass of Table, and that specific operations may require a more specific type.

    Continue reading...

Compartilhe esta Página