0

Here is an example of an interface as seen in the "Fluent Python" book:

from abc import ABCMeta, abstractmethod

class IStream(metaclass=ABCMeta):
    @abstractmethod
    def read(self, maxbytes=-1):
        pass

    @abstractmethod
    def write(self, data):
        pass

A bit further in the book, one can read that "code that explicitly checks for this interface could be written as follows":

def serialize(obj, stream):
    if not isinstance(stream, IStream):
        raise TypeError('Expected an IStream")

My question is: why would I need such isinstance checks? Is it for cases when stream might be an instance of a class that does not inherit from IStream?

Otherwise, my understanding is that it should not be needed, because if the instance that gets passed as stream would not satisfy the IStream interface (by inheriting from the ABC), it would not be allowed to instantiate before it even gets passed:

class Stream(IStream):
  def read(self):
    pass

  def WRITE(self):
    pass

stream = Stream()

Traceback (most recent call last): File "c:\python projects\test.py", line 18, in stream = Stream() TypeError: Can't instantiate abstract class Stream with abstract method write

barciewicz
  • 3,511
  • 6
  • 32
  • 72
  • *"if the instance that gets passed as `stream` would not satisfy the `IStream` interface, it would not be allowed to instantiate before it even gets passed"* — And where do you think that is being enforced? Of course I could instantiate and pass anything I want to `serialize`…?! `serialize(Foo(), list())` – deceze Oct 26 '21 at 06:55
  • "Otherwise, my understanding is that it should not be needed, because if the instance that gets passed as stream would not satisfy the IStream interface, it would not be allowed to instantiate before it even gets passed." Huh? Why not? – juanpa.arrivillaga Oct 26 '21 at 07:04
  • I have updated my question with an example of what I mean (`stream = Stream()` gives an error). – barciewicz Oct 26 '21 at 07:35
  • 1
    It gives an error because you're not overriding the `write` method in `Stream`: it must be lowercase. – enzo Oct 26 '21 at 07:37
  • 2
    The `ABC` ensures that any classes that implements it adheres to its defined interface. In your case you're not implementing a `write` method (you mistyped it as `WRITE`), so that's an error. **That is completely independent of the `serialize` function.** That function accepts *some* arguments, and any caller can pass it *any* arguments. It now tries to explicitly ensure that whatever it gets passed is actually an instance of `IStream`, which in turn would guarantee that it has a `read` and a `write` method, because any instance of `IStream` must have those. – deceze Oct 26 '21 at 07:43
  • @deceze Thanks. So if, in a perfect world, all `stream` classes would implement the ABC interface properly, those `isinstance` checks would not be needed, right? – barciewicz Oct 26 '21 at 08:08
  • 1
    If you could be sure that `stream` would always hold a value that properly implements `IStream`, sure, then you don't need that check. – deceze Oct 26 '21 at 08:12

1 Answers1

1

The metaclass restricts what you're allowed to do / forces you to do certain things when developing a class that implements it.

However, a method like serialize is a method of a class itself, taking an instance as a parameter - someone using your class is in no way forced to pass it an object of the correct class.

An editor or IDE might be able to tell them that they are passing an object of a class that's not expected - even more so when you add clear type hints, but even then it wouldn't be flat out disallowed.

You either need to assume it's used correctly and trust your code to fail with an appropriate exception when your method tries to do something with the passed instance argument that it doesn't support (in the spirit of duck typing), or check for it being of the correct class explicitly with isinstance.

As a rule, I only check using isinstance, if the code I wrote can have potentially disastrous effects simply from having an object with the wrong type passed to it - so pretty rarely. In most cases, type hints are enough.

Grismar
  • 27,561
  • 4
  • 31
  • 54
  • Thanks. So I guess the following assumption is correct: if I (or anyone else working with the code) made sure that `Stream` class implements `IStream` properly and each `stream` is an instance of `Stream`, then `isinstance` checks for `stream` should not be necessary. They might be only needed for cases when something else was passed accidentaly. – barciewicz Oct 26 '21 at 08:02
  • 1
    @barciewicz: Type checking is considered "unpythonic" whereas using `try`/`except` to handle exceptions is. However, allowing the exception to be raised is often the most appropriate response (and the caller can worry about handling them). – martineau Oct 26 '21 at 08:13
  • 2
    The `isinstance` is necessary if it is necessary for `stream` to be an `IStream` - however if someone passes something that's not, but it still has all the methods and attributes needed to *work* as an `IStream`, why do you care? That's what duck typing is all about - checking for the type is unpythonic as @barciewicz mentions, except in cases where it would be harmful not to. But you're correct, if by nature of the implementation they would all be `Stream` instances anywhere, there's no need for the check either. – Grismar Oct 26 '21 at 10:48