-
Notifications
You must be signed in to change notification settings - Fork 288
-
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.
Beta Was this translation helpful? Give feedback.
All reactions
Replies: 2 comments 4 replies
-
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)
Beta Was this translation helpful? Give feedback.
All reactions
-
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.
Beta Was this translation helpful? Give feedback.
All reactions
-
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.
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1
-
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.
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 2
-
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
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1
-
@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
Beta Was this translation helpful? Give feedback.