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
Related
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"))
I need to clarify type, returning by unified factory. I have singleton factory in separate file like this:
from typing import Any, Type
class SingletonFactory:
__slots__ = ('singleton_instance', )
def __init__(self, singleton_class: Type[object], **singleton_init_params: Any):
self.singleton_instance: object = singleton_class(**singleton_init_params) # type: ignore[call-arg] # noqa: E501
def __call__(self) -> object:
return self.singleton_instance
Then in other file.
Option 1:
from typing import Callable, overload
class Client:
pass # IRL have init params
client_factory: Callable[[], Client] = SingletonFactory(
Client
)
client = client_factory() # pyCharm see it as instance of object - expected Client
MyPy error:
error: Incompatible types in assignment (expression has type "SingletonFactory", variable has type "Callable[[], Client]") [assignment]
note: "SingletonFactory.__call__" has type "Callable[[], object]"
Option 2:
from typing import Callable, overload
class Client:
pass # IRL have init params
#overload # type: ignore[misc]
def client_factory() -> Client:
...
client_factory = SingletonFactory(
Client
)
client = client_factory() # pyCharm see it as instance of Client - what I expect
Works, but MyPy error same as above plus: error: Single overload definition, multiple required [misc].
Option 3:
from typing import Callable, overload
class Client:
pass # IRL have init params
class ClientFactory(SingletonFactory):
#overload # type: ignore[misc]
def __call__() -> Client:
...
client_factory = ClientFactory(
Client
)
client = client_factory() # pyCharm see it as instance of Client - what I expect
One less MyPy error, but inheriting SingletonFactory only for overload makes code cumbersome.
Is there any way to fully satisfy MyPy in this situation?
You're looking for generics, to describe a relationship between the types of different variables in your code; one such implementation is demonstrated in the code below.
from typing import Callable, Generic, ParamSpec, TypeVar
P = ParamSpec("P")
T = TypeVar("T")
class SingletonFactory(Generic[T]):
__slots__ = ('singleton_instance', )
singleton_instance: T
def __init__(
self,
singleton_class: Callable[P, T],
*singleton_args: P.args,
**singleton_kwargs: P.kwargs
):
self.singleton_instance = singleton_class(*singleton_args, **singleton_kwargs)
def __call__(self) -> T:
return self.singleton_instance
Now the return type of __call__ is based on the type of the first parameter to __init__:
class Thing: pass
t: Thing = SingletonFactory(Thing)() # OK
Alternatively, you can explicitly specify a factory of a particular type somewhere by providing the generic type:
def my_func(factory: SingletonFactory[int]) -> int:
return factory()
It should be noted that, rather than using Type[T], Callable[P, T] has been used instead, with a ParamSpec (implemented in Python 3.10). When the program is it written this way, it allows the parameters to be checked too, based on the singleton_class:
class OtherThing:
def __init__(self, foo: int, bar: str):
self.foo = foo
self.bar = bar
ot: OtherThing = SingletonFactory(OtherThing)() # error: Missing positional arguments "foo", "bar" in call to "SingletonFactory"
ot = SingletonFactory(OtherThing, 123, bar="baz")() # OK
...and you no longer need type: ignore[call-arg].
MyPy playground
Consider:
from __future__ import annotations
class A:
#classmethod
def get(cls) -> A:
return cls()
class B(A):
pass
def func() -> B: # Line 12
return B.get()
Running mypy on this we get:
$ mypy test.py
test.py:12: error: Incompatible return value type (got "A", expected "B")
Found 1 error in 1 file (checked 1 source file)
Additionally, I have checked to see if old-style recursive annotations work. That is:
# from __future__ import annotations
class A:
#classmethod
def get(cls) -> "A":
# ...
...to no avail.
Of course one could do:
from typing import cast
def func() -> B: # Line 12
return cast(B, B.get())
Every time this case pops up. But I would like to avoid doing that.
How should one go about typing this?
The cls and self parameters are usually inferred by mpyp to avoid a lot of redundant code, but when required they can be specified explicitly by annotations.
In this case the explicit type for the class method would look like the following:
class A:
#classmethod
def get(cls: Type[A]) -> A:
return cls()
So what we really need here is a way to make Type[A] a generic parameter, such that when the class method is called from a child class, you can reference the child class instead. Luckily, we have TypeVar values for this.
Working this into your existing example we will get the following:
from __future__ import annotations
from typing import TypeVar, Type
T = TypeVar('T')
class A:
#classmethod
def get(cls: Type[T]) -> T:
return cls()
class B(A):
pass
def func() -> B:
return B.get()
Now mypy should be your friend again! 😎
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)
How can type hints be declared to indicate that a function returns an instance of the class reference that is passed as an argument?
Declaring it as follows does not seem right, as it indicates that the returned type is the same as the type of the argument:
from typing import TypeVar
T = TypeVar('T')
def my_factory(some_class: T) -> T:
instance_of_some_class = some_class()
return instance_of_some_class
Example usage:
class MyClass:
pass
my_class = my_factory(MyClass) # Inferred type should be MyClass
According to PEP-484, the right way to do this is to use Type[T] for the argument:
from typing import TypeVar, Type
T = TypeVar('T')
def my_factory(some_class: Type[T]) -> T:
instance_of_some_class = some_class()
return instance_of_some_class
It however seems like my editor does not (yet) support this.