Is IOBase a subclass of Iterator?

The official Python documentation specifies that " IOBase (and its subclasses) supports the iterator protocol, meaning that an IOBase object can be iterated over yielding the lines in a stream."

As far as I can tell, this would make IOBase a subclass of Iterator, yet I cannot find anywhere in the official documentation an explicit acknowledgement of this apparent state of affairs. Is there a reason why this is not explicitly acknowledged as fact?

Iterator is a protocol, which means classes that “support the iterator protocol” simply have to implement the right methods. In particular, they do not have to be a subclass of any other class. The Iterator ABC is a convenience, but it is not required that iterators are a subclass of it.

All subtyping in Python is not subclassing, there is also structural subtyping (duck typing). Note also that something that can be iterated over is typically a subtype of Iterable, not Iterator, although an Iterator is also Iterable.

In practice, for a class to produce Iterable objects, it needs to implement either __iter__ or __next__. By doing so it fulfils the interface and becomes a subtype of Iterable.

To be strictly precise: An iterable has to implement __iter__ to return an iterator which has a __next__ method that produces the objects. The iterator should itself be iterable, as itself (ie __iter__ should return self). If a class only implements __next__, it’s not actually going to be iterable.

But yes. It’s all about protocols, not subclassing.

Thank you for the replies. As a logician by trade, I guess I could say that I am less interested in Python’s (formal) notions of “subclass” or “subtype,” whatever those may be, but more in whether a certain class “inherits” behaviors from another class. As such, to my knowledge, IOBase does implement both __next__() and __iter__(), hence, in my logician books, that makes it a logical subclass/subtype of Iterator.

In short, I hereby declare myself satisfied with the answers above, though not without decrying the fact that Python (and quite possibly the whole OOP movement) has chosen to hijack consecrated logical terms such as “(sub)type” and “(sub)class,” to the dismay of logicians worldwide. :slightly_smiling_face:

Logically, is a tomato a fruit? Is a tomato a vegetable? Answering those questions is a matter of looking at the traits of fruits and vegetables, and determining whether a tomato fits. In other words, fruitness is a protocol :slight_smile:

A class/type can, indeed, be a subclass/subtype of two different (though not disjoint(!)) classes/types. Nothing wrong with that.

Subtype, not subclass, if you want to get technical about it.

Note that Iterator (as well as the other “simple” ABCs) overrides the behaviour of isinstance() and issubclass() to return true for any class which implements both __iter__() and __next__(), so IOBase would indeed be treated as a subclass, even though it doesn’t actually inherit directly.

I wish this was documented on the help() text for issubclass, the current wording on Built-in Functions — Python 3.12.3 documentation is much more explicit and clear

edit: though I didn’t check Python 3.13, maybe it’s been fixed

If we’re being “strictly precise”, an iterable has to either implement __iter__ or __getitem__ thanks to the sequence protocol—an unfortunate peculiarity of Python.

1 Like

This is true. And thanks to the two-arg form if iter(), every function and callable object could, in a sense, be considered iterable - although I think that’s pushing it a bit.

If we’re being really precise, one-arg iter takes and iterable and makes an iterator from it, whereas 2-arg iter takes a callable (and a sentinel value) and makes an iterator from it.

Bringing things somewhat back on-topic, once you get to the “strictly precise” level, all that matters ultimately is what the spec says. The original question is more about documentation, expectations and wordings - “As far as I can tell, this would make IOBase a subclass of Iterator”. The OP’s impression (that supporting a protocol implies being a subclass of an ABC) is incorrect, and the question is whether it’s too easy to get that mistaken impression from the documentation (or by inferring from how terms are “commonly used” as opposed to how Python uses them).

I think it’s clear enough from the docs that protocols and ABCs are separate ideas, but it does feel to me that the way typing is incorporated into Python is making it much harder to see that distinction. Typing is adding a layer of strictness and reliance on subtype relationships that isn’t actually how Python works, but which is becoming much more part of how users think Python works.

Personally, I think that’s a shame, but it’s probably too late to stop it happening.

1 Like