Search code examples
pythongenericspython-typing

Generic Factory <-> Good relationship with PEP695 (Self referential generics)


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]):
    ...

Solution

  • 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!