2

Let's say I have the following code:

from typing import Literal, TypeVar, Generic

T = TypeVar("T", bound=str)

class Foo(Generic[T]):
    def foo(self, arg: T = "foo") -> T:
        return arg

The idea is that I have a generic class with a concrete method that takes a string, but the child classes can define a more restrictive input than any string, for example this class will only accept "bar":

class Bar(Foo[Literal["bar"]]):
    pass

However, the original code doesn't pass any type checker. Both mypy and pyright complain. The mypy error is:

test.py:6: error: Incompatible default for argument "arg" (default has type "str", argument has type "T")

I would have thought this would be okay, because T has an upper bound which is a string. However I think the issue is that the default argument "foo" no longer makes sense if the child class accepts only "bar". How then can I define T such that it must always allow the literal "foo", but the child class can also define additional strings it will accept?

2
  • You could I guess make new type that is a union between T and your litteral ? Commented Aug 23, 2022 at 10:01
  • 1
    Oh, good point, I can do ArgType = Union[T, Literal["foo"]] and then def foo(self, arg: ArgType = "foo") -> ArgType. Commented Aug 23, 2022 at 10:21

2 Answers 2

3

In addition to a suggestion from comments, consider using typing.overload:

from typing import TYPE_CHECKING, Literal, TypeVar, Generic, overload
from typing_extensions import reveal_type

T = TypeVar("T", bound=str | Literal["foo"])


class Foo(Generic[T]):
    @overload
    def foo(self, arg: T) -> T:
        ...

    @overload
    def foo(self) -> Literal["foo"]:
        ...

    def foo(self, arg: T | Literal["foo"] = "foo") -> T | Literal["foo"]:
        return arg


class Bar(Foo[Literal["bar"]]):
    pass


if TYPE_CHECKING:
    reveal_type(Bar().foo("bar"))  # Revealed type is "Literal['bar']"
    reveal_type(Bar().foo())  # Revealed type is "Literal['foo']"
Sign up to request clarification or add additional context in comments.

Comments

0

The idea is that I have a generic class with a concrete method that takes a string, but the child classes can define a more restrictive input than any string

This is a violation of the Liskov Substitution Principle (LSP), so mypy and pyright are correct to refuse it. A child class needs to accept anything that the parent class accepts, otherwise you can't freely substitute a child class for the parent class. You can do the opposite though; a child class is allowed to be more permissive than the parent.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.