Vote Charlie!

Alignment in Python code

Posted at age 27.
Edited .

Since my first programming book 15 years ago, I remember the convention of using extra spaces to align the equal signs in lines of variable assignments. This has been pretty standard in most of the languages I’ve used over the years. It’s not a big deal, but it’s something I do instinctively. I struggle much more with other spacing and indentation issues, and seeking guidance, I read Python’s PEP8. And I’m not sure I like with what I found.

Had I spent more time writing code and less time researching, planning projects and building server architectures, perhaps my coding style instincts would be stronger. Or perhaps the increased time in code would mean increased agonizing over the placement of spaces. Either way, I’m not alone, but it seems every programming language has its own community of militants that don’t agree across languages. That makes it especially tricky for someone like me who finds himself using half a dozen different technologies for each project, with multiple overlapping projects.

Here’s an example of spaces to align assignments.

def POST(self):
    logger.debug( 'register.POST' )
    params   = web.input()
    username = params["username"]
    password = params["password"]
    user     = users.register(
            username=username,
            password=users.pswd(password, username),
        )

I also used spaces just inside the parentheses of logger.debug(), but I see now I didn’t do the same for def POST(self) or users.pswd(). I aim for consistency, but since I’ve make a habit of copying and pasting snippets from documentation for the sake of correctness, I inevitably miss some style issues. This issue in particular is something for which I don’t constantly comb my code, as I can’t decide which way is best.

Similarly, I can’t quite decide how I’d like to have that user assignment. Based on what I see on StackOverflow, a typical Python programmer would write:

user = users.register(username=username, password=users.pswd(password, username))

That exceeds the old school 80 character per line limit, but programmers seem to be fairly lax about that, insisting it is an archaic convention for old, narrow consoles and there is no reason for it today. Others of course disagree, pointing out you obviously can fit more files side by side if the code is narrower. Still, it seems the majority of programmers fall on the narrow side. Another comment from Cheezmeister:

I’m with @Enno, a wider screen means room for two 80-column files, not one mostly-80-column-with-a-few-honkin-long-lines and an editor full of unused space :) It’s also important that current editors do have the ability to scroll horizontally so if a line should be more than 80 characters long, it’s perfectly safe to leave it so.

(There’s Python specific discussion falling on the narrow side as well.)

Anyway, I almost always put multiple arguments passed to functions (or items in lists and dictionaries) on separate lines instead. And in languages that allow it, I terminate every such line with a comma, even if it is the last item. This makes it easier to rearrange, add or remove items and see what was changed in version control. But then I can’t decide how much to indent the items, and where to put the closing parentheses and brackets.

For that same example, I typically would have written:

user     = users.register(
    username=username,
    password=users.pswd(password, username),
)

This works a bit better for me since text editors would put you on the proper line after you hit enter, whereas having an extra indentation might result in needing to fix this:

user     = users.register(
        username=username,
        password=users.pswd(password, username),
    )
    user2 = 

Your indentation could get screwed up if you miss this. It’s less likely to happen typing code outright, but when rearranging and changing code, it could get missed. Again, it shouldn’t be a big deal, but I should pick a strategy that will likely result in the fewest hitches down the road.

It gets even more complicated when I have structures like this, which are still pretty simple, but involve a bunch of decisions on where to break lines and how much to indent:

db.accounts.update_one(
    { "token": self.token },
    { "$set": {
            "endpoint":   self.endpoint,
            "token_dict": self.token_dict,
        }
    },
    upsert=True,
)

That’s another one a typical Python example might have all on one line. The module I am actually using there is PyMongo for use with MongoDB. In their documentation, it seems they aren’t even consistent, so I guess I don’t feel so bad.

Here they have items with only one subitem on one line:

result = db.restaurants.update_one(
    {"restaurant_id": "41156888"},
    {"$set": {"address.street": "East 31st Street"}}
)

But then here they have the $set broken up even though it has one subitem. I don’t see the logic here if there is any.

result = db.restaurants.update_one(
    {"name": "Juni"},
    {
        "$set": {
            "cuisine": "American (New)"
        },
        "$currentDate": {"lastModified": True}
    }
)

Notice they have the final closing parenthesis at the same indentation level as result = ….

Now, back to assignment alignment. Python’s official (I think?) coding guidelines have a pet peeves section on whitespace. It says:

Avoid extraneous whitespace in the following situations:

  • Immediately inside parentheses, brackets or braces.
    Yes: spam(ham[1], {eggs: 2})
    No:  spam( ham[ 1 ], { eggs: 2 } )
    
    • More than one space around an assignment (or other) operator to align it with another.

Yes:

    x = 1
    y = 2
    long_variable = 3

No:

    x             = 1
    y             = 2
    long_variable = 3

Both of those were not what I hoped for, but the second actually surprises me.

In the first case, I would have written it in between both ways, like spam( ham[1], {eggs: 2} ). It isn’t totally consistent, but I generally just try to space around parameters without getting carried away. I like it to be totally clear what’s being passed into a function, so often spaces just inside parentheses look good to me. But if it’s a long line with many brackets and operators, the arguments can get lost in the space, in which case I tend to tighten up the spacing from the atomic level outward till it looks nice.

Regarding the alignment, again I would have compromised. In their particular example, I would have done it how they said you should. But if the long_variable were shorter, I would have aligned the spaces. It’s generally a decision based on clarity. Especially if the values being assigned are function calls to the same class, it’s helpful for them to line up. Depending how helpful this is, I may decide to forgo or try harder for alignment.

While often I see Python programmers online advising to break from PEP8 if it results in less readable code, it seems most of the answers on StackOverflow come down against spacing for alignment.

Trusted user Jon Clements wrote:

IMHO - this is less readable (were it valid syntax without quoting the keys), and if I happen to want to add some_longer_named_varible, I’m really not sure if I’d want to muck about re-spacing everything else.

That’s the line of thought I don’t understand. There doesn’t seem to be a notion of judgment based on circumstances. He didn’t say “Any time spent adding a space that isn’t strictly needed is a waste of time.” While I wouldn’t agree, I could at least respect that opinion. But what he said sounded more like “If you start aligning, you will never be able to make an exception, so you’ll either end up with really horrible looking huge spaces sometimes, or you’ll be stuck not being able to update your code at all.”

Another trusted user, delnan, said:

The best practice is to follow PEP 8. Get used to not aligning assignment operators and dictionary values. Your humble opinion may easily change if subjected to working with it each day.

While that wasn’t quite condescending, I expected people wouldn’t care about this so much and every piece of advice would come with a qualifier like: “Whatever is reasonably efficient and looks good is cool, dude!”

Maybe all this is just a distraction from the hard work of coding. But at least for me, spending a few seconds here and there making the product of my labor more beautiful is a small price to pay for personal satisfaction.