Sunday, March 19, 2017

Python: Airing of Grievances

Although this list focuses on the negative aspects of Python, I am publishing it so I can use it as a reference in an upcoming essay about my positive experiences with Python 3.6. Update: This is referenced by my post, "JavaScript vs. Python in 2017."

I am excited by many of the recent improvements in Python, but here are some of my outstanding issues with Python (particularly when compared to JavaScript):

PEP-8

What do you get when you combine 79-column lines, four space indents, and snake case? Incessant line-wrapping, that's what! I prefer 100 cols and 2-space indents for my personal Python projects, but every time a personal project becomes a group project and  true Pythonista joins the team, they inevitably turn on PEP-8 linting with its defaults and reformat everything.

Having to declare self as the first argument of a method

As someone who is not a maintainer of Python, there is not much I could do to fix this, but I did the next best thing: I complained about it on Facebook. In this case, it worked! That is, I provoked Ćukasz Langa to add a check for this (it now exists as B902 in flake8-bugbear), so at least when I inevitably forget to declare self, Nuclide warns me inline as I'm authoring the code.

.pyc files

These things are turds. I get why they are important for production, but I'm tired of seeing them appear in my filetree, adding *.pyc to .gitignore every time I create a new project, etc.

Lack of a standard docblock

Historically, Python has not had a formal type system, so I was eager to document my parameter and return types consistently. If you look at the top-voted answer to “What is the standard Python docstring format?” on StackOverflow, the lack of consensus extremely dissatisfying. Although Javadoc has its flaws, Java's documentation story is orders of magnitude better than that of Python's.

Lack of a Good, free graphical debugger

Under duress, I use pdb. I frequently edit Python on a remote machine, so most of the free tools do not work for me. I have not had the energy to try to set up remote debugging in PyCharm, though admittedly that appears to be the best commercial solution. Instead, I tried to lay the groundwork for a Python debugger in Nuclide, which would support remote development by default. I need to find someone who wants to continue that effort.

Whitespace-delimited

I'm still not 100% sold on this. In practice, this begets all sorts of subtle annoyances. Code snippets that you copy/paste from the Web or an email have to be reformatted before you can run them. Tools that generate Python code incur additional complexity because they have to keep track of the indent level. Editors frequently guess the wrong place to put your cursor when you introduce a new line whereas typing } gives it the signal it needs to un-indent without further interaction. Expressions that span a line frequently have to be wrapped in parentheses in order to parse. The list goes on and on.

Bungled rollout of Python 3

Honestly, this is one of the primary reasons I didn't look at Python seriously for awhile. When Python 3 came out, the rhetoric I remember was: “Nothing substantially new here. Python 2.7 works great for me and the 2to3 migration tool is reportedly buggy. No thanks.” It's rare to see such a large community move forward with such a poorly executed backwards-compatibility story. (Presumably this has contributed to the lack of a default Python 3 installation on OS X, which is another reason that some software shops stick with Python 2.) Angular 2 is the only other similar rage-inducing upgrade in recent history that comes to mind.

I don't understand how imports are resolved

Arguably, this is a personal failure rather than a failure of Python. That said, compared to Java or Node, whatever Python is doing seems incredibly complicated. Do I need an __init__.py file? Do I need to put something in it? If I do have to put something in it, does it have to define __all__? For a language whose mantra is “There's only one way to do it,” it is frustrating that the answer to all of these questions is “it depends.”

Busted Scoping Rules

For a language with built-in map() and filter() functions that underscore the support for first-order functions, I am perplexed with how bad the support for closures is in Python. Apparently your options are (1) lambda (which is extremely limited), or (2) a bastardized closure. Borrowing an example from an article that explains the new nonlocal keyword in Python 3, consider the following code in JavaScript:
function outside() {
  let msg = 'Outside!';
  console.log(msg);
  let inside = () => {
    msg = 'Inside!';
    console.log(msg);
  };
  inside();
  console.log(msg);
}
If you ran outside(), you would get the following output (which you would expect from a lexically scoped language like JavaScript):
Outside!
Inside!
Inside!
Whereas if you wrote this Python code:
def outside():
    msg = 'Outside!'
    print(msg)
    def inside():
        msg = 'Inside!'
        print(msg)
    inside()
    print(msg)
and ran outside(), you would get the following:
Outside!
Inside!
Outside!
In Python 2, the only way to emulate the JavaScript behavior is to use a mutable container type (which is gross):
def outside():
    msg_holder = ['Outside!']
    print(msg_holder[0])
    def inside():
        msg_holder[0] = 'Inside!'
        print(msg_holder[0])
    inside()
    print(msg_holder[0])
Though apparently Python 3 has a new nonlocal keyword that makes this less distasteful:
def outside():
    msg = 'Outside!'
    print(msg)
    def inside():
        nonlocal msg
        msg = 'Inside!'
        print(msg)
    inside()
    print(msg)
I realize that JavaScript generally forces you to type more with its var, let and const qualifiers, but I find this to be a small price to pay in exchange for unambiguous variable scopes.

No comments:

Post a Comment