Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Type alias in generic class using bound type variable #1345

Unanswered
ntessore asked this question in Q&A
Discussion options

I don't quite follow why the following situation is not supported:

from typing import NewType, TypeVar, Generic, TypeAlias, Union, List
Wildcard = NewType('Wildcard', str)
T = TypeVar('T')
class Finder(Generic[T]):
 Pattern: TypeAlias = Union[T, Wildcard]
 # error: "T" is a type variable and only valid in type context [misc]
 # error: Can't use bound type variable "T" to define generic alias [valid-type]
 def index(self, x: List[T], v: Pattern) -> int:
 ...

Simply writing out the alias works fine, of course:

from typing import NewType, TypeVar, Generic, TypeAlias, Union, List
Wildcard = NewType('Wildcard', str)
T = TypeVar('T')
class Finder(Generic[T]):
 def index(self, x: List[T], v: Union[T, Wildcard]) -> int:
 ...

In particular, could someone point me to a reference that explains why I

Can't use bound type variable "T" to define generic alias

because, in my understanding, the fact that T is a bound type variable means precisely that I am not defining a generic alias here.


Edit: For what it's worth, I just saw that the error was introduced in python/mypy#4000 because this is an "ambiguous" situation without further specifying the ambiguity.

You must be logged in to vote

Replies: 2 comments 4 replies

Comment options

I'm guessing because use of the type alias from outside the class would not make sense. As a workaround, you could define a generic type alias and then bind that generic type alias to the type variable. Edit: this has the somewhat nice property of making explicit what parts of method signatures are generic

(I was curious, looks like mypy and pyright match behaviour here)

You must be logged in to vote
4 replies
Comment options

Thanks @hauntsaninja for taking a look.

I'm guessing because use of the type alias from outside the class would not make sense.

But I find it to make perfect sense, because it's bound by the class. Naturally, Finder[int].Pattern is Union[int, Wildcard] and Finder.Pattern is Union[Any, Wildcard].

In essence, I don't understand why the type variable is not functionally equivalent to a concrete type within the class.

As a workaround, you could define a generic type alias and then bind that generic type alias to the type variable.

Something like that is indeed what I am doing, but I find that such type aliases directly in the class would make for much cleaner code.

Comment options

This isn't supported for the same reason that you can't use outer-scoped (bound) type variables for inner-scoped class definitions.

from typing import Generic, TypeVar
T1 = TypeVar("T1")
T2 = TypeVar("T2")
class Foo(Generic[T1]):
 class Bar(dict[T1, T2]): # pyright: T1 is already used by an outer scope
 ...
 reveal_type(Bar()) # mypy: test10.Foo.Bar[Any, Any]

Pyright emits an explicit error in this case. Mypy does not, but it also doesn't handle the case as you might expect. It simply treats T1 as if it's Any.

Until recently, Python type checkers were inconsistent about whether class-scoped type aliases should be allowed. PEP 484 (which introduced the notion of type aliases) and PEP 613 (which introduced TypeAlias) are quiet on this topic. The maintainers of the major type checker came to an agreement that type aliases should be allowed within classes but not within functions.

What you're doing here (attempting to define a class-scoped type alias that uses an outer-scoped bound type variable) is not supported by Python type checkers today. I think it would be an uphill battle to convince all of the type checkers to add such support because it would require a bunch of extra logic (e.g. to handle specialization for member access expressions that are accessing the class-scoped type alias from outside the class — e.g. Finder[int].Pattern in your example) and because this is such a niche use case.

Comment options

Thanks @erictraut, I understand what you are laying out here in the general case, but maybe not in the specific case which I am describing, since the type checker is already able to figure out the type in Finder[int]().Pattern.

Comment options

I'm not understanding what the problem is with @ntessore's example either. This seems pretty well defined to me? (edit: bad word choice; if it's not defined in any PEP then it's literally not defined; what I meant was it seems to have one clear & straightforward expected result.) I only made it to googling and writing this when this little experiment almost worked (I was very surprised the Foo.T syntax worked at all, but then didn't allow U).

class Foo(Generic[FValue]):
 T: TypeAlias = int
 # U: TypeAlias = FValue # the mypy error under discussion
CoolT: TypeAlias = Foo.T
X: CoolT = 4
# Y: CoolT = 'string' # properly gets 'incompatible types' error

My use-case is I'm trying to write an interpreter, which seems to be the worst spot of benefiting immensely from type hints (otherwise stupid subtle bugs everywhere forever) but the number of generics needed makes the code pretty much unreadable. @ntessore's example is exactly what I'd love to use here. My 0ドル.02

Comment options

@ntessore can you post your solution here? I am also running into this issue.

My solution here is to use a classproperty decorator to store the union in a method return type.
The classproperty decorator allows one to use SomeCLass.output_types to get the results:

import dataclasses
import typing
import typing_extensions
T = typing.TypeVar('T')
U = typing.TypeVar('U')
class classproperty(typing.Generic[T]):
 def __init__(self, method: typing.Callable[..., T]):
 self.__method = method
 functools.update_wrapper(self, method) # type: ignore
 def __get__(self, obj, cls=None) -> T:
 if cls is None:
 cls = type(obj)
 return self.__method(cls)
@dataclasses.dataclass(frozen=True)
class AnyTypeSchema(Schema[T, U]):
 # Python representation of a schema defined as true or {}
 @classproperty
 def output_types(cls) -> typing.Union[
 T,
 str,
 int,
 float,
 bool,
 None,
 U,
 bytes,
 FileIO
 ]:
 args = typing_extensions.get_args(cls)
 return typing.Union[
 args[0], # type: ignore
 str,
 int,
 float,
 bool,
 None,
 args[1], # type: ignore
 bytes,
 FileIO
 ] # type: ignore
X = AnyTypeSchema[dict, tuple]
X.output_types # shows dict | str | float | bool | tuple | bytes | FileIO
You must be logged in to vote
0 replies
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

AltStyle によって変換されたページ (->オリジナル) /