How to express this kind of relationship between types with PEP695 annotations? Pylance (pyright) says that Self is not valid in this context
, but there seems to be no other way to express it?
The goal is to have AbstractFactory
know what kind of goods it produces, and have AbstractGood
know which kind of AbstractFactory
produced it.
# factory.py
from typing import Self
from abc import ABC
class AbstractFactory[G: AbstractGood[Self]](ABC):
def __init__(self, good_class: type[G]) -> None:
self.good_class = good_class
def produce(self) -> G:
good = self.good_class(self)
return good
class AbstractGood[F: AbstractFactory[Self]](ABC):
def __init__(self, factory: F):
self.factory = factory
# charlie.py
from factory import AbstractFactory, AbstractGood
class ChocolateFactory(AbstractFactory[ChocolateBar]):
...
class ChocolateBar(AbstractGood[ChocolateFactory]):
...
Just running some tests on this, it seems like the below achieves something very close to what you're looking for:
# factory.py
from abc import ABC
class AbstractFactory[Good: AbstractGood](ABC):
def __init__(self, good_class: type[Good]) -> None:
self.good_class = good_class
def produce(self) -> Good:
return self.good_class(self)
class AbstractGood[Factory: AbstractFactory](ABC):
def __init__(self, factory: Factory):
self.factory = factory
# charlie.py
from factory import AbstractFactory, AbstractGood
class ChocolateFactory(AbstractFactory["ChocolateBar"]):
...
class ChocolateBar(AbstractGood[ChocolateFactory]):
...
produced_item = ChocolateFactory(ChocolateBar).produce() # Is of type ChocolateBar
underlying_factory = produced_item.factory # Is of type ChocolateFactory
Perhaps I'm missing something, but I think the only check you're missing here is (in the inheritance section) if ChocolateFactory
receives ChocolateBar
, ChocolateBar
must receive ChocolateFactory
. I don't think that would be possible without Higher-Kinded Types. I'm also not sure how it would work, as the reference would be circular in terms of the class definitions.
Personally I would be looking to try and remove the dependency on self.factory
in AbstractGood
. Without this, the typing becomes non-circular and it also probably would make the code clearer. Hope this is useful!