Hello,
So i was wondering in the following code if there is a way to infer in Model2 the target type from the provider type.
from dataclasses import dataclass
@dataclass
class Provider[T]():
field: T
def get(self) -> T:
return self.field
@dataclass
class Dependence[P: Provider[T], T]():
provider: P
def target(self) -> T:
return self.provider.get()
# Way that works but need to provide twice the "int" type
@dataclass
class Model:
dep: Dependence[Provider[int], int]
m = Model(Dependence(Provider(42)))
reveal_type(m.dep.target()) # Typed as int correctly
@dataclass
class Model2:
dep: Dependence[Provider[int]]
m = Model2(Dependence(Provider(42)))
reveal_type(m.dep.target()) # Typed as Any
I would like Dependence to be generic only over the Provider type and use the Provider generic type in the Dependence class. I would like to know if this is somehow possible to express this using type hints ?
This is currently not possible. In general, you’d need higher kinded type variables for this, which we currently don’t have in python. They do get brought up somewhat regularly, but actually making them work would require a lot of effort and they unfortunately also are not super well understood outside of typing circles and don’t really give you that much extra stuff in a language like python. So I wouldn’t hold my breath for them to come out. What you’re already doing is basically the best workaround we have unfortunately.
The lack of this feature makes it cumbersome when trying to compose various generic types together as you either have inference that doesn’t work or you have to copy and paste the type params from the child objects up through all the parents.
The example of TypeScript shows that all these kinds of things are possible, and arguably necessary for very dynamic languages like JS and Python.
This can be done by matching on self in Dependence.target():
from dataclasses import dataclass
from typing import Any
@dataclass
class Provider[T]:
field: T
def get(self) -> T:
return self.field
@dataclass
class Dependence[P: Provider[Any]]:
provider: P
def target[T](self: Dependence[Provider[T]]) -> T:
return self.provider.get()
@dataclass
class Model:
dep: Dependence[Provider[int]]
m = Model(Dependence(Provider(42)))
reveal_type(m.dep.target()) # int
As long as we are handing out advice… I have been watching this thread since I have a similar situation, but I don’t think can be handled in the same way as above. I’d love to learn there’s some happy, clever Python approach that I just haven’t been able to think up.
In the code below, I’d really like to be able to declare class DocumentEvent[K] or class DocumentEvent[D] instead of having to manually coordinate both together. In TypeScript that would definitely be possible to derive one of those from the other with a type expression.
from enum import Enum
from typing import Literal, TypedDict, TypeVar, TypedDict
class Kind(Enum):
RootAdded = "RootAdded"
RootRemoved = "RootRemoved"
class RootAdded(TypedDict):
kind: Literal["RootAdded"]
stuff: str
class RootRemoved(TypedDict):
kind: Literal["RootRemoved"]
stuff: int
K = TypeVar("K", bound=Kind)
D = TypeVar("D")
class DocumentEvent[K, D]: # sad-face
kind: K
def to_serializable(self) -> D: ...
class RootAddedEvent(DocumentEvent[Kind.RootAdded, RootAdded]):
...
I’d be happy to define a property for kind, say, but I’d need to be able to derive K from D.
I can have a stab at this. We’re missing some constraints here (maybe vis-a-vis serialization?), so until we have those I can suggest the simplest possible version:
from dataclasses import dataclass
@dataclass
class RootAddedEvent:
stuff: str
@dataclass
class RootRemovedEvent:
stuff: int
type DocumentEvent = RootAddedEvent | RootRemovedEvent
Thanks @jorenham,
So far it seems your solution does actually work for the need I raised. Never thought about using type hints on self that may open more solutions to this kind of problems in the future.
In my typing quest I have also encountered the same issue as @bryevdv and I ended up accepting that the only solution we currently have is the one provided by @Tinche but that is not fullfilling the goal completly.
As far as I know in Python type hinting the only way there is to properly do type mappings is on functions with the overload decorator. I have read some criticism about the verbosity of it on functions and I doesn’t solve the more general type mapping concept we have here.
@Tinche The contraints are to have event classes that can report their (enumerated) “kind” and that can produce typed dict representations of themselves for serializing that also include the “kind” in the dict, since the runtime on the other end of the wire will need that in order to re-consituted the model.
I figured as much - usually I deal with the wire format in the serialization step. I find modeling the kind thing in the class itself is redundant (the “tag” in the tagged union here, so to speak, is event.__class__). I’ve had a lot of success modeling the serialization format and the pure domain model separately, and it also makes typing simpler. If this appeals to you we can take it to DMs.
Right, the typed dict is a bit of an “intermediate” format. As always the reason for something like that is “history”. Bokeh is ~15 years old at this point so there’s only so much movement I can make on this part of the codebase without a lot of disruption. This was just an attempt (hope) to tighten up typing where it might be possible in the code that exists, but I think specifying both type parameters here is unavoidable.