On Fri, May 15, 2020 at 01:00:09PM -0700, Christopher Barker wrote:
I know you winked there, but frankly, there isn't a clear most Pythonic API here. Surely you do'nt think PYhton should have no methods?
That's not what I said. Of course Python should have methods -- it's an OOP language after all, and it's pretty hard to have objects unless they have behaviour (methods). Objects with no behaviour are just structs.
But seriously, and this time no winking, Python's design philosophy is very different from that of Java and even Ruby and protocols are a hugely important part of that. Python without protocols wouldn't be Python, and it would be a much lesser language.
[Aside: despite what the Zen says, I think *protocols* are far more important to Python than *namespaces*.]
Python tends to have shallow inheritance hierarchies; Java has deep ones. Likewise Ruby tends to have related classes inherit from generic superclasses that provide default implementations.
In we were like Ruby, there would be no problem: we'd just add a view method to something like object.Collections.Sequence and instantly all lists, tuples, range objects, strings, bytes, bytearrays etc would have that method. But we're not. In practice, each type would have to implement it's own view method.
Python tends to use protocol-based top-level functions:
len, int, str, repr, bool, iter, list
etc are all based on *protocols*, not inheritance. The most notable counter-example to that was `iterator.next` which turned out to be a mistake and was changed in Python 3 to become a protocol based on a dunder.
That's not to say that methods aren't sometimes appropriate, or that there may not be grey areas where we could go either way. But in general, the use of protocols is such a notable part of Python, and so unusual in other OOP languages, that it trips up newcomers often enough that there is a FAQ about it:
although the answer is woefully incomplete. See here for a longer version:
There is a *lot* of hate for Python's use of protocols, especially among people who have drunk the "not real object oriented" Koolaid, e.g. see comments here:
where this is described as "moronic". Let me be absolutely clear here: the use of protocols, as Python does, is a *brilliant* design, not a flaw, and in my opinion the haters are falling into the Blub trap:
Using protocols looks moronic to them because they haven't seen how they add more power to the language and the coder. All they see are the ugly underscores. Why write a `__len__` method instead of a `len` method? There's no difference except four extra characters.
That's some real Blub thinking right there.
Unfortunately, len() hardly takes advantage of the possibilities of protocols, so it's an *obvious* example but not a *good* example. Here's a better example:
py> class NoContains: ... def __getitem__(self, idx): ... if idx < 10: ... return 1000+idx ... raise IndexError ... py> 1005 in NoContains() True
I wrote a class that doesn't define or inherit a `__contains__` method, but I got support for the `in` operator for free just by supporting subscripting. If you don't understand protocols, this is just weird. But that's your loss, not a design flaw.
Another good example is `next()`. When I write an iterator class, I can supply a `__next__` dunder. All it needs to do is provide the next value.
I've never needed to add support for default values in a `__next__` method, because the builtin `next()` handles it for me:
_SENTINEL = object() try: ... except StopIteration: if default is not _SENTINEL: return default raise
I get support for default values for free, thanks to the use of a protocol. If this were Python 2, with a `next` method, I'd have needed to write those six lines a couple of hundred times so far in my life, plus tests, plus documentation. Multiply that by tens of thousands of Python coders.
Some day, if the next() builtin grows new functionality to change the exception raised:
not one single iterator class out of a million in the world will need to change a single line of code in order to get the new functionality.
This is amazingly powerful stuff when handled properly, and len() is perhaps the most boring and trivial example of it.
I'm going to be provocative: if (generic) you are not blown away by the possibilities of protocols, you don't understand them.