最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

generics - Type hinting Python inheritance “Base classes of [child] are mutually incompatible” - Stack Overflow

programmeradmin1浏览0评论

I'm trying to learn how to use base classes and inheritance. I'm getting type-checking errors, but the code is running as expected. Do I have type checker problems, type hinting problems, or meaningful code problems?

Here I try a generic class that inherits from the abstract base class Sequence (and ABC—is that necessary?). Then I make child classes where that sequence is specifically a list or a tuple. Pylance/pyright is happy with this and it runs fine:

from __future__ import annotations
from abc import ABC
from collections.abc import Iterable, Sequence

class Row[T](Sequence[T], ABC):
    def __init__(self, iterable: Iterable[T]):
        ...

class Row_Mutable[T](list[T], Row[T]):
    def __init__(self, iterable):
        super().__init__(iterable)

class Row_Immutable[T](tuple[T], Row[T]):
    def __init__(self, iterable):
        super().__init__(iterable)

row_mut_int: Row_Mutable[int] = Row_Mutable(range(10))
row_mut_str: Row_Mutable[str] = Row_Mutable('abcdefg')
row_imm_int: Row_Immutable[int] = Row_Immutable(range(10))
row_imm_str: Row_Immutable[str] = Row_Immutable('abcdefg')

Now a 2D version. This is a Sequence of Sequences, so the children are list of lists and tuple of tuples:

class Grid[T](Sequence[Sequence[T]], ABC):
    def __init__(self, iterable: Iterable[Iterable[T]]):
        ...

class Grid_Mutable[T](list[list[T]], Grid[T]):
    def __init__(self, iter_of_iter):
        super().__init__(list(row) for row in iter_of_iter)

class Grid_Immutable[T](tuple[tuple[T]], Grid[T]):
    def __init__(self, iter_of_iter):
        super().__init__(tuple(row) for row in iter_of_iter)

Now Pylance/pyright calls out the class definitions:

Base classes of Grid_Mutable are mutually incompatible
Base class "Grid[T@Grid_Mutable]" derives from "Sequence[Sequence[T@Grid_Mutable]]" which is incompatible with type "Sequence[list[T@Grid_Mutable]]"

... and the equivalent for the tuple version.

How is Sequence[Sequence[T]] incompatible with Sequence[list[T]]? How should I code and hint something like this?

Copilot suggested the T = TypeVar('T') form rather than Grid[T]; that slightly changed the errors but didn't resolve them. I'm running Python 3.13 and I'm not concerned about backwards compatibility—more about best practices and Pythonicness.

I'm trying to learn how to use base classes and inheritance. I'm getting type-checking errors, but the code is running as expected. Do I have type checker problems, type hinting problems, or meaningful code problems?

Here I try a generic class that inherits from the abstract base class Sequence (and ABC—is that necessary?). Then I make child classes where that sequence is specifically a list or a tuple. Pylance/pyright is happy with this and it runs fine:

from __future__ import annotations
from abc import ABC
from collections.abc import Iterable, Sequence

class Row[T](Sequence[T], ABC):
    def __init__(self, iterable: Iterable[T]):
        ...

class Row_Mutable[T](list[T], Row[T]):
    def __init__(self, iterable):
        super().__init__(iterable)

class Row_Immutable[T](tuple[T], Row[T]):
    def __init__(self, iterable):
        super().__init__(iterable)

row_mut_int: Row_Mutable[int] = Row_Mutable(range(10))
row_mut_str: Row_Mutable[str] = Row_Mutable('abcdefg')
row_imm_int: Row_Immutable[int] = Row_Immutable(range(10))
row_imm_str: Row_Immutable[str] = Row_Immutable('abcdefg')

Now a 2D version. This is a Sequence of Sequences, so the children are list of lists and tuple of tuples:

class Grid[T](Sequence[Sequence[T]], ABC):
    def __init__(self, iterable: Iterable[Iterable[T]]):
        ...

class Grid_Mutable[T](list[list[T]], Grid[T]):
    def __init__(self, iter_of_iter):
        super().__init__(list(row) for row in iter_of_iter)

class Grid_Immutable[T](tuple[tuple[T]], Grid[T]):
    def __init__(self, iter_of_iter):
        super().__init__(tuple(row) for row in iter_of_iter)

Now Pylance/pyright calls out the class definitions:

Base classes of Grid_Mutable are mutually incompatible
Base class "Grid[T@Grid_Mutable]" derives from "Sequence[Sequence[T@Grid_Mutable]]" which is incompatible with type "Sequence[list[T@Grid_Mutable]]"

... and the equivalent for the tuple version.

How is Sequence[Sequence[T]] incompatible with Sequence[list[T]]? How should I code and hint something like this?

Copilot suggested the T = TypeVar('T') form rather than Grid[T]; that slightly changed the errors but didn't resolve them. I'm running Python 3.13 and I'm not concerned about backwards compatibility—more about best practices and Pythonicness.

Share Improve this question edited Jan 18 at 12:48 Daraan 4,0877 gold badges22 silver badges46 bronze badges asked Jan 17 at 23:57 JacktoseJacktose 7337 silver badges22 bronze badges
Add a comment  | 

3 Answers 3

Reset to default 1

This looks like a Pylance bug. Your code is fine, and mypy accepts it without complaint.

I asked the maintainers of Pyright. Here's the reply (emphasis mine):

I think pyright is correct here. You should pick one or the other as a base class, not both.

[...]

If you swap the two base classes in your MutableGrid and ImmutableGrid examples, they becomes sound. That's because list[list[T] and tuple[tuple[T, ...]] are both subtypes of Sequence[Sequence[T]]. Ordering matters here because later base classes override earlier ones.

It is perhaps better not to inherit from list and tuple in this case, as they bring more trouble than they are worth. Instead, prefer composition:

class MutableGrid[T](Grid[T]):
    _cells: list[list[T]]

This is not a compatible case. From a runtime perspective you (rather) want list/tuple to be your parent class, however from a typing perspective you want the Sequence methods to be your direct parents, otherwise list/tuple will be hardly-typed into your signatures and pyright picks of the incompatibilities in the signatures of Sequence (typing.pyi) and list/tuple builtins.pyi.

You can silence the incompatibilities by reversing the order


class Grid[T](Sequence[Sequence[T]], ABC):
    def __init__(self, iterable: Iterable[Iterable[T]]):
        ...

class Grid_Mutable[T](Grid[T], list[list[T]]):  # instead of list[list[T]], Grid[T]
    def __init__(self, iter_of_iter): ...

However this will make Sequence your runtime parent class.

Solutions:

  1. Use only one of them as your base class
  • subclassing builtin classes can often be avoided
  1. Separate runtime vs. type-checking by using Grid[T] if TYPE_CHECKING else list as parents, however I do not think that this is justified here
  2. Rely on duck-typing, write your Sequence/Protocol classes for static type-checking, but at runtime strictly use unmodified list/tuple objects.
发布评论

评论列表(0)

  1. 暂无评论