typing_extensions icon indicating copy to clipboard operation
typing_extensions copied to clipboard

Bug: `typing_extensions.Generic` not support `TypeVar` defaults

Open IvanKirpichnikov opened this issue 3 months ago • 10 comments

Work MRE.

Setup

>> python version: 3.13.5 (main, Jun 21 2025, 09:35:00) [GCC 15.1.1 20250425]

MRE

from typing import TypeVar, Generic, get_args

R = TypeVar("R")
T = TypeVar("T", default=int)

class A(Generic[R, T]):
    pass

assert get_args(A[str]) == (str, int)

Not work MRE.

Setup

>> python version: 3.10.17 (main, Apr  9 2025, 04:03:39) [Clang 20.1.0 ]

>> uv pip show typing-extensions
Name: typing-extensions
Version: 4.15.0

MRE

from typing_extensions import TypeVar, Generic, get_args

R = TypeVar("R")
T = TypeVar("T", default=int)

class A(Generic[R, T]):
    pass

assert get_args(A[str]) == (str,)

IvanKirpichnikov avatar Sep 03 '25 18:09 IvanKirpichnikov

The assertions you listed differ in both cases. I would think one wants the same code in both cases? (str, int) is the current behavior on Python 3.13 / 3.14.

Under typing_extensions 4.14.1 it returns only (str,) with 4.15.0 it also returns the default value similar to 3.13 typing.

To my knowledge it is the intended behavior to get the all arguments get_args with defaults substituted in.

Daraan avatar Sep 03 '25 18:09 Daraan

This only seems to affect typing_extensions on Python 3.9 and 3.10. This script:

from typing_extensions import TypeVar, Generic, get_args
R = TypeVar("R")
T = TypeVar("T", default=int)
class A(Generic[R, T]): ...

print(f"{get_args(A[str])=}    {A[str].__args__=}")

produces:

$ uv run --python 3.11 temp.py
get_args(A[str])=(<class 'str'>, <class 'int'>)    A[str].__args__=(<class 'str'>, <class 'int'>)

$ uv run --python 3.10 temp.py
get_args(A[str])=(<class 'str'>,)    A[str].__args__=(<class 'str'>,)

Based on a cursor look with a debugger, the behavior difference comes from inside Generic.__class_getitem__. Fixing this might require monkey patching it on Python 3.9 & 3.10

brianschubert avatar Sep 03 '25 19:09 brianschubert

I think we don't need monkey pathing here. It is enough to write your own Generic object that will be able to use TypeVar defaults

At the moment, typing_extensions uses the mechanism of re-exporting the Generic class from the original typing package

IvanKirpichnikov avatar Sep 04 '25 12:09 IvanKirpichnikov

Is there any news on this bug? Should I send a pull request with a bug fix?

IvanKirpichnikov avatar Sep 13 '25 15:09 IvanKirpichnikov

MRE with the new generics syntax:

# temp.py
from typing_extensions import get_args


class Foo[T = str]:
    x: T

f = Foo()

try:
    reveal_type(f.x)
except NameError:
    pass

print(get_args(f))
$ mypy temp.py 
temp.py:9: note: Revealed type is "builtins.str"
Success: no issues found in 1 source file
$ python temp.py
()

jacksonthall22 avatar Oct 09 '25 21:10 jacksonthall22

@jacksonthall22 that's unrelated (your example doesn't involve typing-extensions). You need to call get_args() on the class, not an instance.

@IvanKirpichnikov PRs welcome! We'll have to see how invasive the fix is; it only affects 3.9 (which is about to go EOL) and 3.10 (which is also getting towards the end of its support).

JelleZijlstra avatar Oct 10 '25 00:10 JelleZijlstra

@IvanKirpichnikov PRs welcome! We'll have to see how invasive the fix is; it only affects 3.9 (which is about to go EOL) and 3.10 (which is also getting towards the end of its support).

I didn't understand the argument about 3.10. Type var defaults appeared in 3.13. That is, you need to make your typing_extensions.Generic to EOL 3.13. No?

IvanKirpichnikov avatar Oct 11 '25 11:10 IvanKirpichnikov

If I understand the previous discussion correctly, this bug appears only in 3.9 and 3.10. If it instead appears up to 3.12, that does make it more important to fix.

JelleZijlstra avatar Oct 11 '25 14:10 JelleZijlstra

It appears on versions <3.13(that is 3.10, 3.11, 3.12) because typing_extensions.Generic is literally typing.Generic

I think this option is critical because it requires working with default value options in input extensions.

IvanKirpichnikov avatar Oct 11 '25 14:10 IvanKirpichnikov

On Python 3.11 and 3.12 with typing-extensions, get_args() returns the same thing as on 3.13+ with typing (as @brianschubert reported a few comments up). If you think something is still wrong on 3.11 and 3.12, please report exactly what you expect to happen and what is happening instead.

because typing_extensions.Generic is literally typing.Generic

That's an implementation detail; we don't necessarily have to duplicate Generic itself to solve this issue. We care only about the publicly visible behavior.

JelleZijlstra avatar Oct 11 '25 14:10 JelleZijlstra