Right, this was my thought.

Can’t you just use a typing.Protocol on __getitem__ here?

https://typing.python.org/en/latest/spec/protocol.html

Something like

    from typing import Protocol

    class Indexable(Protocol):
        def __getitem__(self, i: int) -> Self: ...
Though maybe numpy slicing needs a bit more work to support

Indeed.

IMO, the trick to really enjoying python typing is to understand it on its own terms and really get comfortable with generics and protocols.

That being said, especially for library developers, the not-yet-existant intersection type [1] can prove particularly frustrating. For example, a very frequent pattern for me is writing a decorator that adds an attribute to a function or class, and then returns the original function or class. This is impossible to type hint correctly, and as a result, anywhere I need to access the attribute I end up writing a separate "intersectable" class and writing either a typeguard or calling cast to temporarily transform the decorated object to the intersectable type.

Also, the second you start to try and implement a library that uses runtime types, you've come to the part of the map where someone should have written HERE BE DRAGONS in big scary letters. So there's that too.

So it's not without its rough edges, and protocols and overloads can be a bit verbose, but by and large once you really learn it and get used to it, I personally find that even just the value of the annotations as documentation is useful enough to justify the added work adding them.

[1] https://github.com/python/typing/issues/213

Slicing is totally hintable as well.

Change the declaration to:

def __getitem__(self, i: int | slice)

Though to be honest I am more concerned about that function that accepts a wild variety of objects that seem to be from different domains...

I'd guess inside the function is a HUGE ladder of 'if isinstance()' to handle the various types and special processing needed. Which is totally reeking of code smell.