How would I use TypeVarTuple for this example?
T = TypeVar(“T”)
Ts = TypeVarTuple(“Ts”)
#dataclass
class S(Generic[T]):
data: T
def data_from_s(*structs: ??) -> ??:
return tuple(x.data for x in structs)
a = data_from_s(S(1), S(“3”)) # is type tuple[int, str]
I don't see any way to do this with the current spec. The main issue I see is that TypeVarTuple does not support bounds. You can't constrain the types referred to by Ts to be bounded to S.
You need to translate somehow tuple[S[T1], S[T2], ...] -> tuple[T1, T2, ...], but you have no way to know that the types contained by Ts are specializations of S or that types themselves are generic with further parameterization.
Without using TypeVarTuple, your goal can be accomplished to some extent with a pattern like the following, using overload to handle subsets of the signature for differing amounts of arguments. I also use an ending / in the overloads to prevent usage of named arguments (forcing positional args to be used), which allows the overloads to match the real method definition.
Obviously, this pattern becomes awkward as you add more ranges of arguments, but in some cases it can be a nice escape hatch.
from dataclasses import dataclass
from typing import Any, Generic, TypeVar, assert_type, overload
T = TypeVar("T")
#dataclass
class S(Generic[T]):
data: T
...
T1 = TypeVar("T1")
T2 = TypeVar("T2")
T3 = TypeVar("T3")
#overload
def data_from_s(s1: S[T1], /) -> tuple[T1]:
...
#overload
def data_from_s(s1: S[T1], s2: S[T2], /) -> tuple[T1, T2]:
...
#overload
def data_from_s(s1: S[T1], s2: S[T2], s3: S[T3], /) -> tuple[T1, T2, T3]:
...
def data_from_s(*structs: S[Any]) -> tuple[Any, ...]:
return tuple(x.data for x in structs)
Which will pass this test:
assert_type(
data_from_s(S(1)),
tuple[int]
)
assert_type(
data_from_s(S(1), S("3")),
tuple[int, str]
)
assert_type(
data_from_s(S(1), S("3"), S(3.9)),
tuple[int, str, float]
)
I don't fully understand the problem but removing ?? solves the problem.
from dataclasses import dataclass
from typing import Generic, TypeVar, TypeVarTuple
T = TypeVar("T")
Ts = TypeVarTuple("Ts")
#dataclass
class S(Generic[T]):
data: T
def data_from_s(*structs):
return tuple(x.data for x in structs)
a = data_from_s(S(1), S("3")) # is type tuple[int, str]
or put T in place of ??
from dataclasses import dataclass
from typing import Generic, TypeVar, TypeVarTuple
T = TypeVar("T")
Ts = TypeVarTuple("Ts")
#dataclass
class S(Generic[T]):
data: T
def data_from_s(*structs: T) -> T:
return tuple(x.data for x in structs)
a = data_from_s(S(1), S("3")) # is type tuple[int, str]
or something like this, this sample has not any issue with mypy if you add --enable-incomplete-feature=TypeVarTuple parameter and call it like mypy test.py --enable-incomplete-feature=TypeVarTuple
from dataclasses import dataclass
from typing import Generic, TypeVar, TypeVarTuple
T = TypeVar("T")
Ts = TypeVarTuple("Ts")
#dataclass
class S(Generic[T]):
data: T
def data_from_s(*structs: S) -> tuple:
return tuple(x.data for x in structs)
a = data_from_s(S(1), S("3"))
Related
class A:
var_a: bool = False
var_b: int | str # str if var_a is True, else int
a = A()
a.var_a = True
a.var_b # should be str
How can I type this so that mypy knows var_b should be a string if var_a is True? Is this possible? Maybe with some clever usage of Literal[True] or Literal[False]?
Silvio Mayolo is right, this is a dependent pair, and it's not supported in Python. Here it is in Idris. However, you're right that you can very vaguely approximate behaviour like this with Literal (and overload). That said, don't. This example is horrible in a number of ways.
from typing import TypeVar, Literal, Generic, overload
Bool = TypeVar("Bool", Literal[True], Literal[False])
class A(Generic[Bool]):
#overload
def __init__(self: A[Literal[True]], a: Literal[True], b: int): ...
#overload
def __init__(self: A[Literal[False]], a: Literal[False], b: str): ...
def __init__(self, a: Literal[True, False], b: str | int):
self._var_a = a
self._var_b = b
#property
def var_a(self) -> Literal[True, False]:
return self._var_a
#overload
def var_b(self: A[Literal[True]]) -> int: ...
#overload
def var_b(self: A[Literal[False]]) -> str: ...
def var_b(self):
return self._var_b
a = A(True, 0)
reveal_type(a.var_b()) # main.py:31: note: Revealed type is "builtins.int"
You may be able to massage this to work with your exact example through property.
You don't really need var_a at all. You can make A generic, and let its concrete type be set when you initialize it with an appropriate value for var_b,
from typing import TypeVar, Generic
T = TypeVar('T', int, str)
class A(Generic[T]):
var: T
def __init__(self, x: T):
self.var = x
a1 = A(5) # OK, bind T to int
a2 = A("foo") # OK, bind T to str
# a3 = A(3.195) # ERROR, T cannot be bound to float
reveal_type(a1.var) # builtins.int
reveal_type(a2.var) # builtins.str
reveal_type(A) # See below
reveal_type(a1) # A[int]
reveal_type(a2) # A[str]
The revealed type for A itself can be read roughly as "A maps type T to type A[T], as long as T is str or int.
I am trying to type hint the arguments of a function that takes a callable, and has a default argument (in the example below set)
from typing import Callable, List
T = TypeVar("T")
def transform(data: List[int], ret_type: Callable[[List[int]], T] = set) -> T:
return ret_type(data)
a = [1, 2, 3]
my_set: Set = transform(a)
The above code triggers the following error message from mypy mypy3: Incompatible default for argument "ret_type" (default has type "Type[Set[Any]]", argument has type "Callable[[List[int]], T]")
What should be the correct type of ret_type ?
EDIT
The below code although not ideal works fine (cf #chepner comment)
from typing import cast, Any, Callable, TypeVar
T = TypeVar("T")
def transform(data: Any, ret_type: Callable[..., T] = cast(Callable, set)) -> T:
return ret_type(data)
You could use the #overload for correctly type hinting of function with default argument for your case:
from typing import Callable, List, TypeVar, overload, Set
T = TypeVar("T")
#overload
def transform(data: List[int]) -> Set[int]: ...
#overload
def transform(data: List[int], ret_type: Callable[[List[int]], T]) -> T: ...
# untyped implementation
def transform(data, ret_type = set):
return ret_type(data)
a = [1, 2, 3]
my_set: Set = transform(a)
Let's say I have a class like this:
class myclass:
def __init__ (self, param1: Tuple[str,...], param2: bool) -> None:
self.member1 = param1
self.member2 = param2
self.member3 = 10
def gimmie(self) -> int | Tuple[str,...]:
return self.member1 if self.member2 else self.member3
Is there any way I can ensure that the return from gimmie is not of type int | Tuple[str,...] but rather is an int or Tuple[str,...]?
Edit:
There are a couple answers that involve significant acrobatics to do this, when all I really was looking to do was cast the return. Each of those answers both comment on a code "smell" because of this.
The problem is simply that I construct an object with a flag and one of the methods returns 1 of 2 types based on that flag. If that's bad design, what would be the "correct" way to do it?
Here is a way to solve this with generics:
from __future__ import annotations
from typing import overload, Literal, Generic, TypeVar, cast
T = TypeVar('T')
class myclass(Generic[T]):
member1: tuple[str, ...]
member2: bool
member3: int
#overload
def __init__(self: myclass[tuple[str, ...]], param1: tuple[str, ...], param2: Literal[True]) -> None:
...
#overload
def __init__(self: myclass[int], param1: tuple[str, ...], param2: Literal[False]) -> None:
...
def __init__(self, param1: tuple[str, ...], param2: bool) -> None:
self.member1 = param1
self.member2 = param2
self.member3 = 10
def gimmie(self) -> T:
return cast(T, self.member1 if self.member2 else self.member3)
reveal_type(myclass(('a', 'b'), True).gimmie())
# note: Revealed type is "builtins.tuple*[builtins.str]"
reveal_type(myclass(('a', 'b'), False).gimmie())
# note: Revealed type is "builtins.int*"
Some notes:
This approach requires annotating the self argument to give it a different static type. Usually, we don't annotate self, so make sure not to forget this!
Sadly I could not get a if b else c to have the right type without adding a cast.
I do agree with Samwise that this kind of type judo is a code smell, and might be hiding problems with the design of your project.
Here's one way to tackle it with subclasses and an #overloaded factory function:
from typing import Literal, Tuple, Union, cast, overload
class MyClass:
def __init__(self, param1: Tuple[str, ...], param2: bool) -> None:
self.member1 = param1
self.__member2 = param2
self.member3 = 10
def gimmie(self) -> Union[int, Tuple[str, ...]]:
return self.member1 if self.__member2 else self.member3
class _MySubclass1(MyClass):
def gimmie(self) -> Tuple[str, ...]:
return cast(Tuple[str, ...], MyClass.gimmie(self))
class _MySubclass2(MyClass):
def gimmie(self) -> int:
return cast(int, MyClass.gimmie(self))
#overload
def myclass(param1: Tuple[str, ...], param2: Literal[True]) -> _MySubclass1:
...
#overload
def myclass(param1: Tuple[str, ...], param2: Literal[False]) -> _MySubclass2:
...
def myclass(param1: Tuple[str, ...], param2: bool) -> MyClass:
if param2:
return _MySubclass1(param1, param2)
else:
return _MySubclass2(param1, param2)
myobj1 = myclass((), True)
myobj2 = myclass((), False)
reveal_type(myobj1.gimmie()) # Revealed type is "builtins.tuple[builtins.str]"
reveal_type(myobj2.gimmie()) # Revealed type is "builtins.int"
Note that this is a lot of work and requires careful attention to make sure the casts match the implementation logic -- I don't know the real-world problem you're trying to solve, but having to go through this much trouble to make the typing line up correctly is often a "smell" in the way you're modeling the data.
I have such class:
from typing import Generic, TypeVar
from pydantic import BaseModel
T = TypeVar("T", bound=BaseModel)
class A(Generic[T]):
def some_method(self) -> T:
... # some data processing
return T.from_orm(some_data_model)
# in tests package:
class Model(BaseModel): ...
A[Model].some_method()
assert Model.from_orm.called_once
But it doesn't work because T is not a real type(BaseModel) but only TypeVar that bounds to BaseModel.
So the question is: how can I get the real(substituted) type T?
Important notes:
it doesn't matter for me what the type the method is(classmethod, method of object instance or staticmethod)
my python version is 3.8
This answer doesn't helped me:
I tried this:
from typing import ..., get_args
# ...
def some_method(self) -> T:
...
return get_args(type(self))[0].from_orm(some_data_model)
This:
from typing import ..., get_args
# ...
#classmethod
def some_method(cls) -> T:
...
return get_args(cls)[0].from_orm(some_data_model)
And this:
from typing import ..., get_args
# ...
#classmethod
def some_method(cls) -> T:
...
return get_args(A)[0].from_orm(some_data_model)
And I call it in a such way(if it is class/static method):
A[Model].some_method()
And if it is instance method:
A[Model]().some_method()
And all of it(without calling from_orm and getting index 0) just returns empty tuple and the whole method ends with IndexError(which is totally right).
In the comments #alex_noname posted a link to another such question.
In my case the answer was to use the __orig_class__ attribute of the self object:
from typing import Generic, TypeVar, get_args
from pydantic import BaseModel
T = TypeVar("T", bound=BaseModel)
class A(Generic[T]):
def some_method(self) -> T:
... # some data processing
return get_args(self.__orig_class__)[0].from_orm(some_data_model)
For more information please refer to this answer
Suppose I want to write a generic class using mypy, but the type argument for the class is itself a generic type. For example:
from typing import TypeVar, Generic, Callable
A = TypeVar("A")
B = TypeVar("B")
T = TypeVar("T")
class FunctorInstance(Generic[T]):
def __init__(self, map: Callable[[Callable[[A], B], T[A]], T[B]]):
self._map = map
def map(self, x: T[A], f: Callable[[A], B]) -> T[B]:
return self._map(f, x)
When I try to call mypy in the definition above I get an error:
$ mypy typeclasses.py
typeclasses.py:9: error: Type variable "T" used with arguments
typeclasses.py:12: error: Type variable "T" used with arguments
I tried adding constraints to the T TypeVar's definition but failed to make this work. Is it possible to do this?
Currently, as of writing, the mypy project does not support higher-kinded types. See the following github issue:
https://github.com/python/typing/issues/548
The returns package now provides some third party support for HKTs.
To copy a snippet from their docs
>>> from returns.primitives.hkt import Kind1
>>> from returns.interfaces.container import Container1
>>> from typing import TypeVar
>>> T = TypeVar('T', bound=Container1)
>>> def to_str(arg: Kind1[T, int]) -> Kind1[T, str]:
... ...
Your Functor would be sth like
from typing import TypeVar, Generic, Callable
A = TypeVar("A")
B = TypeVar("B")
T = TypeVar("T")
class FunctorInstance(Generic[T]):
def __init__(
self, map: Callable[[Callable[[A], B], Kind1[T, A]], Kind1[T, B]]
):
self._map = map
def map(self, x: Kind1[T, A], f: Callable[[A], B]) -> Kind1[T, B]:
return self._map(f, x)