I'm struggling to type hint correctly a python dataclass (or any class) with partial initialization, it seems that the type is lost somewhere but not sure where:
from dataclasses import dataclass
from functools import partial
@dataclass
class Test:
a : int
b : int
@classmethod
def test_partial[T: Test](cls: type[T], b:int) -> partial[T]:
return partial(cls, b=b)
@classmethod
def test_partial2(cls, b:int):
return partial(cls, b=b)
part = Test.test_partial(3)
part( # linter suggest nothing when passing here
part2 =partial(Test, b=3)
part2( # linter suggest a=...,b=... when passing
# interestingly enough letting out the typehint, the linter knows again
part = Test.test_partial2(3)
part( # linter suggest a=...,b=... when passing
The interesting thing is that pyright get's it right when I don't give it to them, so probably there is a way. Any clue?
PD: regarding to the similar question, notice that I'm not asking how to typehint in a mypy compliant maner (no error) but in a maner that tells pyright (or mypy) the arguments that are left.
so solutions such as -> Any: or -> partial[...] may work in the other case but not in this one. and also protocols are not ok as need to be dynamic.
Several issues here:
Q: How to typehint functools.partial classes?
You don't, because functools.partial
is not expressible with standard type annotations. It is special cased in both mypy and pyright; you can see the special-casing algorithm in their respective repositories, e.g.:
To get correct types for a callable built from functools.partial
, you must not provide annotations.
Q: The interesting thing is that both mypy and typeright get's it right when I don't give it to them
This doesn't sound right for mypy. mypy and pyright act fundamentally different here for your example:
@classmethod
def test_partial2(cls, b:int):
return partial(cls, b=b)
mypy requires type annotations to be added to the return type to correctly infer the type in the caller scope, while pyright automatically infers the return type if a return type annotation is not provided. If you don't add a return type annotation, like here in test_partial2
, mypy infers the return type to be Any
.
Therefore, only pyright is capable of correctly inferring the type in the calling scope of test_partial2
, because it doesn't require a type annotation. It's almost* impossible for mypy to correctly infer the type here, because any return type annotation added by the user erases the special casing done by mypy for functools.partial
.
I say almost impossible, because technically it is possible by implementing a decorator factory which takes a functools.partial
expression to transform the function defined underneath, but this solution could look much more complicated than it's worth to solve the original problem.