Michal Charemza
Michal Charemza

Reputation: 27012

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?

Upvotes: 1

Views: 988

Answers (1)

Mario Ishac
Mario Ishac

Reputation: 5887

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.

Upvotes: 1

Related Questions