stouputils.typing.extension_points module#

Decorators marking how a class is meant to be extended.

inheritable() flags a class designed to be subclassed, overridable() a working default a subclass may replace, and hook() a method called at a fixed point of a flow, whose default does nothing so a subclass can act there. They only set a boolean attribute and never wrap what they decorate, so they cost nothing at call time.

@inheritable
class Trainer:
        def fit(self) -> None:
                self.before_epoch()
                self.train_epoch()

        @hook
        def before_epoch(self) -> None: ...

        @overridable
        def train_epoch(self) -> None:
                ...
INHERITABLE_ATTRIBUTE: str = '__is_inheritable__'[source]#

Attribute set to True on a class decorated with inheritable().

OVERRIDABLE_ATTRIBUTE: str = '__is_overridable__'[source]#

Attribute set to True on the function behind a member decorated with overridable().

HOOK_ATTRIBUTE: str = '__is_hook__'[source]#

Attribute set to True on the function behind a member decorated with hook().

ClassMember = ClassMember[source]#

A type alias for anything defined in a class body that can be decorated

inheritable(cls: T) → T[source]#

Mark a class as meant to be subclassed, sets __is_inheritable__ to True.

Nothing is enforced at runtime: it only tells the reader that subclassing is part of the class contract.

>>> @inheritable
... class Base:
...     pass
>>> Base.__is_inheritable__
True
overridable(member: T) → T[source]#

Mark a member as a default implementation that subclasses may replace, sets __is_overridable__ to True.

Unlike hook(), the default already does the job, and a subclass swaps it for another way of doing it. Nothing is enforced at runtime and the member is not wrapped. Stack it above @property, @classmethod or @staticmethod: the flag lands on the underlying function.

>>> class Base:
...     @overridable
...     def run(self) -> None: ...
...
...     @overridable
...     @property
...     def name(self) -> str: return "base"
...
...     @overridable
...     @classmethod
...     def create(cls) -> None:
...         ...
>>> Base.run.__is_overridable__, Base.name.fget.__is_overridable__, Base.create.__is_overridable__
(True, True, True)
hook(member: T) → T[source]#

Mark a method as a hook, called at a fixed point of a flow, sets __is_hook__ to True.

The caller may be the class itself, or code driving it from outside, such as a trainer calling a model. Unlike overridable(), the default does nothing, or hands its input back unchanged: a subclass adds behaviour at that point. Nothing is enforced at runtime and the member is not wrapped.

>>> class Runner:
...     @hook
...     def before_run(self) -> None: ...
>>> Runner.before_run.__is_hook__
True
set_member_flag(
member: ClassMember,
attribute: str,
) → None[source]#

Set attribute to True on the function behind a class member.

Parameters:

member – Function, property (its getter is flagged), classmethod or staticmethod (their __func__ is flagged)