Search code examples
pythonmypypython-typing

Call constructor of type parameter in generic class


I'm writing a generic class over AnyStr, so allowing bytes or str.

class MyObject(Generic[AnyStr]):
   ...

Inside (multiple) methods of this class, I would like to construct the empty bytes or empty string object, b'' or '', depending on the type parameter. How can I do this?


Solution

  • You should have a base class with the shared methods applying to both str and bytes that take advantage of common behavior (for example, both str and bytes having length, or both str and bytes being indexable), and two subclasses providing implementations for the specific behaviors. To force the subclasses to provide those specific behaviors (such that mypy can assume a call to their specific methods would succeed in the base class), you make an equivalent @abstractmethod in the base class.

    Here's how it all looks like:

    from abc import abstractmethod, ABC
    from typing import AnyStr, Generic, final
    
    class MyObject(ABC, Generic[AnyStr]):
        @classmethod
        @abstractmethod
        def empty(cls) -> AnyStr:
            pass
    
        def __init__(self, data: AnyStr):
            self.data: AnyStr = data
    
        # Example shared method.
        def is_empty(self) -> bool:
            # Assume that for the sake of the example we can't do `len(self.data) == 0`, and that we need
            # to check against `empty()` instead.
            return self.data == self.__class__.empty()
    
    class MyStr(MyObject[str]):
        @classmethod
        @final
        def empty(cls) -> str:
            return ""
    
    class MyBytes(MyObject[bytes]):
        @classmethod
        @final
        def empty(cls) -> bytes:
            return b""
    

    We make empty() a class method instead of an instance method because it doesn't depend on an instance with particular data to know what an empty str / bytes looks like.

    Additionally, we make empty() a final method so subclasses of either MyStr or MyBytes` that want to further provide specific behavior don't get to change what is considered "empty" (as there is only one thing that can be considered empty).

    All of above will typecheck under mypy --strict.

    On the caller side, they would never instantiate MyObject[str] or MyObject[bytes] (in fact, mypy will prevent that, as we would want, because MyObject doesn't have an implementation for empty()). Instead, because you said in comments that caller will know ahead of time whether they want bytes or str, they instantiate MyStr or MyBytes directly.