Python 3 is a mess. How did this happen?

Python3 is a mess. How did this happen? So many of the changes seem to me to fly in the face of the whole Zen of Python aesthetic. The two biggest offenders, in my opinion, are asyncio and type hints.

Armin Ronacher's excellent post I don't understand asyncio pretty well covers the garbage fire that is twisted asyncio. It's a tangle of complexity that even GvR struggles (oh so painfully) to explain:

Libraries need to be rewritten to speak the new asyncio flavor of Python, and you'll need to absolutely litter your code with additional keyword noise. So much for readability counts.

A commenter on HN summed it up quite well:

I think I've made the point a few times that I don't like this style of programming at all, because the coroutine layer turns into an Inner Platform replicating all the control-flow structures the original language has, which then has to integrate with the original language which causes more than twice the complexity to emerge.

For fun, though, let's just take a peek at what adding two numbers looks like in the world of async:

import asyncio

async def compute(x, y):
    print("Compute %s + %s ..." % (x, y))
    await asyncio.sleep(1.0)
    return x + y

async def print_sum(x, y):
    result = await compute(x, y)
    print("%s + %s = %s" % (x, y, result))

loop = asyncio.get_event_loop()
loop.run_until_complete(print_sum(1, 2))


How to add 2 integers, asynchronously

I can't wait to to start writing my very own tasks, coroutines, protocols, event loops, transports, streams, futures... Gee, do you think Django will be async any time soon?

If the signal-to-noise ratio wasn't already low enough for you, Python has decided to add type hints to the language as well. They're called hints because they are not enforced by the interpreter -- you'll need a separate tool for that -- but while you're at it you might as well just use golang (which, by the way, has a very nice language-level concurrency primitives).

Here's how you might declare a function:

def async_query(on_success: Callable[[int], None],
                on_error: Callable[[int, Exception], None]) -> None:
    # Body

That's fine and well when you're using basic types, but Python is a dynamic will we handle generics, you ask? Something like this:

from typing import TypeVar, Generic, Sized, Iterable, Container, Tuple

T = TypeVar('T')

class LinkedList(Sized, Generic[T]):

K = TypeVar('K')
V = TypeVar('V')

class MyMapping(Iterable[Tuple[K, V]],
                Container[Tuple[K, V]],
                Generic[K, V]):

Don't get me started on "f-strings" or the decision to break iteration for all the builtin types...


Comments (4)

Roger | feb 19 2017, at 12:04am

It's worth adding the main reason Python3 was created (unicode-by-default, and, small cleanups). They didn't get it right. They blame the timeframe of the Python3000 talks, but the correct way to handle unicode is to make everything a byte string, assumed encoding being UTF8. The only way to salvage the situation from all the mistakes is to create a Python4 that removes about half of the standard library (including asyncio and optional typing) and brings Python to a simple, small footprint. That won't happen, because CPython is a toy project for GvR and a small band of rogue developers who don't truly care about good engineering.

Emil Stenström | feb 15 2017, at 01:52am

I don't think asyncio and typing are reasons to stick with Python 2. If you take your initial example, it can now be written as

def hello_world(name):
    print(f'Hello {name}')

... which I think makes a lot more sense. So the language has become better for people that want to keep it simple (most of us), too.

Charles | feb 14 2017, at 02:24pm

The change has been gradual; first there was yield from, now there's async/await, who knows what tomorrow might bring?

The standard library is definitely a mixed bag, but I'd also argue that multiprocessing/asyncore/xml isn't integrated at a language level like asyncio or type annotations are. What makes matters worse is that all this async stuff used to have a separate life as the twisted project -- it's not exactly like this stuff didn't exist until Python 3.5 or whatever. Lastly, lxml has a C dependency (libxml2), while I'm guessing anything in the standard library works with other interpreters.

Ionel Cristian Mărieș | feb 14 2017, at 12:30pm

This might make one believe there was some sudden change of direction within Python maintainership, however, truth be told Python accumulated various now-useless and/or controversial features since the beginning. Things like multiprocessing (simply put, the architecture ain't sound - too easy to get deadlocks), asyncore (now considered arcane), xml (is there something lxml doesn't do much better?) or tkinter (would something like that get accepted into stdlib today? hard to imagine) don't seem much different than asyncio or type annotations.

So by those standards Python 2 is an insane language, just like Python 3. Lets not be hypocrites :-)

The truth is that just like any other piece of software Python accumulates bloat.

Commenting has been closed, but please feel free to contact me