вот по поводу протоколов, кстати.
я сейчас активно внедряю тайп хинты. там, где есть DI, работаю примерно по такой схеме:
class AbstractSomething(ABC): pass
class ConcreteSomething(AbstractSomething): pass
def do_something(something: AbstractSomething): pass
do_something(something=ConcreteSomething())
смысл - я хочу, чтобы
а) из хинта было понятно, что конкретно ожидается в плане интерфейса
и
б) если нужно реализовать что-то свое, то с помощью pycharm можно быстро найти существующие реализации наследований от AbstractSomething, и на их основе понять, что и как нужно делать.
что смущает?
часто я передаю в do_something простые по сути обработчики (не лямбды, конечно, но вполне реализуемые в одном def без замыканий), и в качестве хинта достаточно было бы указать Callable[[args, kwargs], None], вместо того, чтобы городить весь этот огород с ABC.
но тогда не удовлетворяется хотелка б) - статический анализатор сработает, но по факту человеку будет сложнее разобраться с происходящим.
что вы думаете? (б) критично? или это оверинжиниринг?