Abstract base class or typing.Protocol for supporting __str__

1.9k views Asked by At

Is there a builtin ABC for enforcing __str__ to be implemented in subclasses? Or a typing protocol?

I want a function that only accepts classes with __str__ __hash__ and __eq__. I've found Hashable but not Stringable

2

There are 2 answers

1
Paweł Rubin On

There is no such built-in ABC. In fact, every class has this method inherited from object:

The default implementation defined by the built-in type object calls object.repr().

See docs.


In [1]: class Foo: pass

In [2]: str(Foo())
Out[2]: '<__main__.Foo object at 0x7fcf10e219f0>'

In [3]: print(Foo())
<__main__.Foo object at 0x7fcf10e23d00>

In [4]: print(Foo().__str__())
<__main__.Foo object at 0x7fcf10e20d60>

In [5]: print(Foo().__repr__())
<__main__.Foo object at 0x7fcf10e20af0>

In [6]: object().__repr__()
Out[6]: '<object object at 0x7fcf119c6810>'

In [7]: object().__str__()
Out[7]: '<object object at 0x7fcf119c67c0>'
0
Nuno André On

There's no such a builtin abstract class, but you can enforce those requirements.

from abc import ABC, abstractmethod


class Required(ABC):
    @abstractmethod
    def __str__(self) -> str:
        ...

    @abstractmethod
    def __hash__(self) -> int:
        ...

    @abstractmethod
    def __eq__(self, other) -> bool:
        ...
>>> class Impl(Required): ...
>>> i = Impl()
TypeError: Can't instantiate abstract class Impl with abstract methods __eq__, __hash__, __str__

Also, you could check a specific structural subtyping for equality at runtime, and return a TypeError if it's not the case (but it may not be a best practice):

from typing import Protocol, runtime_checkable


@runtime_checkable
class HasValue(Protocol):
    value: int


class Impl(Required):
    # also define __str__ and __hash__

    @property
    def value(self):
        return 42

    def __eq__(self, other):
        if not isinstance(other, HasValue):
            raise TypeError
        return self.value == other.value


class Valued:
    value = 42


class NotValued:
    ...
>>> i = Impl()
>>> v = Valued()
>>> n = NotValued()
>>> i == v  # both have self.value
True
>>> v == n  # self.value not enforced
False
>>> i == n  # self.value enforced
TypeError