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:

https://docs.python.org/3/faq/design.html#why-does-python-use-methods-for-some-functionality-e-g-list-index-but-functions-for-other-e-g-len-list

although the answer is woefully incomplete. See here for a longer 
version:

http://effbot.org/pyfaq/why-does-python-use-methods-for-some-functionality-e-g-list-index-but-functions-for-other-e-g-len-list.htm

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:

https://stackoverflow.com/questions/237128/why-does-python-code-use-len-function-instead-of-a-length-method

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:

http://paulgraham.com/avg.html

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:

    next(iterator, raise_instead=ValueError)

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.



-- 
Steven
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/GIO3E7D63HGJ7QQZDYKRPVV3URENVW4I/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to