← Back to context

Comment by calibas

3 years ago

Does anybody have an example of when __subclasshook__ is truly useful? Something that can't be accomplish more coherently with a simple function?

Personally, I'd prefer that isinstance() and issubclass() have predictable behavior.

A lot of these dunder (double underscore) functions are useful for metaprogramming. Just kinda spitballing an idea but perhaps some code to generate a python object model based on the schema of a database would want to use this method. If your DB schema has some special way of defining subclass relationships (maybe a foreign key to another table) you might need to manually control when something is or isn't a subclass in python's object model based on the result of querying the DB schema.

In general metaprogramming is the kind of thing you probably don't and shouldn't reach for first, in fact it's usually more for libraries and tools vs. your production business logic. It can get difficult to reason about and pass the maintenance of code that heavily uses metaprogramming to other people unfamiliar with it.

The most dominate use (these days, at least) is to implement structural typing (a la Protocol), i.e. conforming to a “shape” without actually inheriting anything. So yeah, it’s not particularly useful for day-to-day use, but still a hook needed to make certain nice things happen behind the scenes.

  • it allows fast unpacking and parameter retrieval align with the usual type checks.

    the other day, this allowed me to refactor a 300L in 50 which are actually readable

> Something that can't be accomplish more coherently with a simple function?

I think an example of `Iterable` (sort of like the one in the article) is a very ham-fisted way of getting this sort of check into Python code.

In the end, doesn't the difference just boil down to

    class Iterable(ABC):

        @classmethod
        def __subclasshook__(cls, C):
            return hasattr(C, "__iter__")

vs.

    def is_iterable(t: Type):
        return hasattr(t, "__iter__")
 

Where the first makes it harder to use `Iterable` incorrectly (i.e. supplying a non-type as parameter).

I can imagine that a Java or C# programmer would call the first version more "coherent" because it gives the interface `Iterable` a name explicitly.

Sort of like there's not reaaaaally a reason to use Extension Methods in C# (of course there are, but in a lot of simple scenarios there aren't) as opposed to static methods taking a Type as single parameter.

  • The difference is that if the first one is defined, it can be used like this without cluttering up the function body:

      def foo(bar: Iterable):
        pass

  • > Sort of like there's not reaaaaally a reason to use Extension Methods in C#

    “but it will be so cool!” worked for me :)