Skip to content

Default of TypeVar isn't respected for assignments. #18904

@s3rius

Description

@s3rius

Hello, here's an issue I stumbled upon.

from typing import Generic, Type
from typing_extensions import TypeVar


class Interface: ...


_T = TypeVar("_T", bound=Interface, default=Interface)


class MyGeneric(Generic[_T]):
    def __init__(self) -> None:
        super().__init__()
        self._value: Type[_T] = Interface

In this example, I expect this code to be valid, because I have a default value for _T and don't have it in constructor.
Also, while playing around I noticed that default doesn't work with default argument in __init__ either.

from typing import Generic, Type
from typing_extensions import TypeVar


class Interface: ...


_T = TypeVar("_T", bound=Interface, default=Interface)


class MyGeneric(Generic[_T]):
    def __init__(self, my_val: Type[_T] = Interface) -> None:
        super().__init__()
        self._value = my_val

I'd expect this to be valid code because we define the default for the Type[_T] argument before the object is initialized, so it should fall back to the default of TypeVar in that case. At least, that's what I would expect.

Is this intended behavior?

Activity

brianschubert

brianschubert commented on Apr 9, 2025

@brianschubert
Member

I think mypy is correct to reject this. The assignment self._value: Type[_T] = Interface isn't sound, _T since _T may be bound to a subtype of Interface. Consider for example:

class FooInterface(Interface):
    @staticmethod
    def foo() -> None: ...

class FooGeneric(MyGeneric[FooInterface]): ...

x = FooGeneric()
x._value.foo()  # boom! 
s3rius

s3rius commented on Apr 9, 2025

@s3rius
Author

Thank you. That's a good point. But in that case, how can I implement such a logic, that would force subclasses to obey type they declare?

added and removed
bugmypy got something wrong
on Apr 9, 2025
A5rocks

A5rocks commented on Apr 13, 2025

@A5rocks
Collaborator

You might be interested in #3737 regarding the second example. It's not completely clear what you want from the first example so I can't offer a work around.

But for the second you could try an overload and self-types I think?

sterliakov

sterliakov commented on Apr 22, 2025

@sterliakov
Collaborator

This is very similar to #3737 and #18812, the latter exactly about typevar default and defaults in generic classes.

force subclasses to obey type they declare

Do you need to provide any default implementation? I'd recommend going a level deeper and providing the default implementation that depends on _T in a separate subclass. Remove Interface default argument, let subclasses take care of that. Something like (untested)

from typing import Generic
from typing_extensions import TypeVar

class Interface: ...

_T = TypeVar("_T", bound=Interface, default=Interface)

class MyGeneric(Generic[_T]):
    _value: type[_T]

class FooInterface(Interface):
    foo: int
    
class FooGeneric(MyGeneric[FooInterface]):
    def __init__(self) -> None:
        super().__init__()
        self._value = FooInterface
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @s3rius@brianschubert@A5rocks@sterliakov

        Issue actions

          Default of TypeVar isn't respected for assignments. · Issue #18904 · python/mypy