Comment by purplehat_

Comment by purplehat_ 3 days ago

14 replies

Could someone explain just what's so bad about this?

My best guess is that it adds complexity and makes code harder to read in a goto-style way where you can't reason locally about local things, but it feels like the author has a much more negative view ("crimes", "god no", "dark beating heart", the elmo gif).

xg15 3 days ago

Maybe I have too much of a "strongly typed language" view here, but I understood the utility of isinstance() as verifying that an object is, well, an instance of that class - so that subsequent code can safely interact with that object, call class-specific methods, rely on class-specific invariants, etc.

This also makes life directly easier for me as a programmer, because I know in what code files I have to look to understand the behavior of that object.

Even linters use it to that purpose, e.g. resolving call sites by looking at the last isinstance() statement to determine the type.

__subclasshook__ puts this at risk by letting a class lie about its instances.

As an example, consider this class:

  class Everything(ABC):

    @classmethod
    def __subclasshook__(cls, C):
      return True

    def foo(self):
      ...
You can now write code like this:

  if isinstance(x, Everything):
    x.foo()
A linter would pass this code without warnings, because it assumes that the if block is only entered if x is in fact an instance of Everything and therefore has the foo() method.

But what really happens is that the block is entered for any kind of object, and objects that don't happen to have a foo() method will throw an exception.

  • cauthon 3 days ago

    You _can_ write pathological code like the Everything example, but I can see this feature being helpful if used responsibly.

    It essentially allows the user to check if a class implements an interface, without explicitly inheriting ABC or Protocol. It’s up to the user to ensure the body of the case doesn’t reference any methods or attributes not guaranteed by the subclass hook, but that’s not necessarily bad, just less safe.

    All things have a place and time.

    • dragonwriter 3 days ago

      > It essentially allows the user to check if a class implements an interface, without explicitly inheriting ABC or Protocol.

      Protocols don't need to be explicit superclasses for compile time checks, or for runtime checks if they opt-in with @runtime_checkable, but Protocols are also much newer than __subclass_hook__.

      • cauthon 3 days ago

        TIL, thanks!

        (I love being wrong on HN, always learn something)

    • kccqzy 3 days ago

      I don't think so. I think the other code should just stop using isinstance checks and switch to some custom function. I personally think isinstance checks benefit from having its behavior simpler and less dynamic.

      > check if a class implements an interface, without explicitly inheriting ABC or Protocol

      This really doesn't sound like a feature that belongs in the language. Go do something custom if you really want it.

    • Someone 3 days ago

      But the moment you use a third party library, you cannot use it “responsibly” because that library, too, might use it “responsibly”, and then, you can easily get spooky interaction at a distance, with bugs that are hard or even impossible to fix.

    • Doxin 3 days ago

      A good example being stuff like isinstance(x, Iterable) and friends. Figuring out if something is iterable is a bit of a palaver otherwise.

taeric 3 days ago

I took the memes as largely for comedic effect, only?

I do think there is a ton of indirection going on in the code that I would not immediately think to look for. As the post stated, could be a good reason for this in some things. But it would be the opposite of aiming for boring code, at that point.

danudey 3 days ago

TL;DR having a class that determines if some other class is a subclass of itself based off of arbitrary logic and then using that arbitrary logic to categorize other people's arbitrary classes at runtime is sociopathic.

Some of these examples are similar in effect to what you might do in other languages, where you define an 'interface' and then you check to see if this class follows that interface. For example, you could define an interface DistancePoint which has the fields x and y and a distance() method, and then say "If this object implements this interface, then go ahead and do X".

Other examples, though, are more along the lines of if you implemented an interface but instead of the interface constraints being 'this class has this method' the interface constraints are 'today is Tuesday'. That's an asinine concept, which is what makes this crimes and also hilarious.

  • Spivak 3 days ago

    You better not find out about Protocols in Python then. The behavior you describe is exactly how duck typing / "structural subtyping" works. Your class will be an instance of Iterable if you implement the right methods having never known the Iterable class exists.

    I don't find using __subclasshook__ to implement structural subtyping that you can't express with Protocols/ABCs alone to be that much of a crime. You can do evil with it but I can perform evil with any language feature.

    • jonahx 3 days ago

      > You better not find out about Protocols in Python then. The behavior you describe is exactly how duck typing / "structural subtyping" works. Your class will be an instance of Iterable if you implement the right methods having never known the Iterable class exists.

      Conforming to an interface is a widely accepted concept across many popular languages. __subclasshook__ magic is not. So there is a big difference in violating the principle of least surprise.

      That said, I'd be curious to hear a legitimate example of using it to implement "structural subtyping that you can't express with Protocols/ABCs alone".

      • dragonwriter 3 days ago

        > That said, I'd be curious to hear a legitimate example of using it to implement "structural subtyping that you can't express with Protocols/ABCs alone".

        ABCs with __subclasshook__ have been available since Python 2.6, providing a mechanism to inplement runtime-testable structural subtyping. Protocols and @runtime_checkable, which provide typechecking-time structural subtyping (Protocols) that can also be available at runtime (with @runtime_checkable) were added in Python 3.8, roughly 11 years later.

        There may not be much reason to use __subclasshook__ in new code, but there's a pretty good reason it exists.

        • jonahx 3 days ago

          > There may not be much reason to use __subclasshook__ in new code, but there's a pretty good reason it exists.

          That's quite a different claim, and makes a lot of sense. Thanks for the history!