Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
s3rius opened this issue Apr 9, 2025 · 4 comments
Closed

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

s3rius opened this issue Apr 9, 2025 · 4 comments
Labels
pending Issues that may be closed question

Comments

@s3rius
Copy link

s3rius commented Apr 9, 2025

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?

@s3rius s3rius added the bug mypy got something wrong label Apr 9, 2025
@brianschubert
Copy link
Collaborator

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! 

@brianschubert brianschubert added the pending Issues that may be closed label Apr 9, 2025
@s3rius
Copy link
Author

s3rius commented Apr 9, 2025

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?

@sterliakov sterliakov added question and removed bug mypy got something wrong labels Apr 9, 2025
@A5rocks
Copy link
Collaborator

A5rocks commented Apr 13, 2025

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
Copy link
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
Labels
pending Issues that may be closed question
Projects
None yet
Development

No branches or pull requests

4 participants