Tabs vs spaces

There are some important decisions that you have to make at the beginning of the project. And I’m not talking about: How to architecture your application? Use micro-services or built a monolith? Which DB engine to use? How are you going to deploy and scale your application?

No, before we get to that, there are other things that we need to sort out, especially if we are working with other developers. Questions like: Tabs or Spaces? How many characters per line? Should we use single-quotes or double-quotes for strings? Absolute of relative imports?

For some people, it’s easier to agree on the overall architecture than to settle on one of those minor details.

Different programmers have different preferences on how to write code. And usually, the more experience someone has, the more likely they are to think “their way” of writing code is the best. And the fact that you can write a whole Python program on one line (without exceptions though!) doesn’t help.

PEP8 and PEP257

That’s why we have PEP8. It’s a style guide for writing Python code, written by Guido (with the help of others). Together with PEP257 - the docstring style guide - they give us a solid foundation for how to write and document our code.

Even if it’s the first time you hear about those documents, if you are using the auto-formatting in your code editor, you are probably following those rules anyway.

PEP8 documents, for example:

  • How many spaces you should use for indentation (4 spaces)
  • How to indent the closing parentheses or brackets
  • What’s the recommended line length (79 characters for code, 72 characters for comment and docstrings)
  • How to sort “import” statements (there are three groups), etc.

Unless you have a good excuse, you should follow those rules. The most common exception nowadays is the line length. Our screens are getting bigger and bigger, so increasing the 79 characters per line limit makes sense.

Sometimes PEP8 is not enough

But even with PEP8, there is still plenty of room to argue. How do you split functions that have multiple lines - how many spaces should you use for “hanging parenthesis”? Are you going to use single quotes or double quotes? You can waste a lot of time during code reviews on those small details.

And a code style is only effective if everyone on your team is following it. So everyone has to install some tools that will point out the errors and then fix them manually. Some people don’t like it (they are “too busy” to bother with code styles).

Black

The best way to solve those problems is to use a tool called black. Black takes your files and formats then according to the PEP8 and PEP257 with some additional rules on top of that. For example, it will convert all single quotes to double quotes. You can see black in action here: https://black.now.sh.

Black offers almost no customization. The only settings that you should be able to change is the line length if you don’t like the default 88 characters. There are some other options like the --skip-string-normalization (to preserve single quotes if you like them), but according to the documentation, you should not use it. This option is meant for adding black to an existing project if you want to prevent it from changing almost every line where there is a string.

So black is opinionated. Most people like it that way. Some people don’t (their main argument for a long time was the “single quote to double quote” conversion). I worked with people who wouldn’t merge a Pull Request because they didn’t like the code formatting. Black takes those silly discussions away and helps you focus on what’s really important during code reviews. So I’m going to stick with it. And, unless you know what you are doing (it’s much harder to enforce a “custom” programming style) - you should probably also use black.

How to install and use black?

You can install black with pip or pipx:

$ pip install black
# Or even better
$ pipx install black

And then run it on files or directories that you want to format:

$ black my_project
reformatted my_project/my_project/cli.py
reformatted my_project/my_project/__init__.py
reformatted my_project/setup.py
reformatted my_project/tests/test_my_project.py
reformatted my_project/docs/conf.py
All done! ✨ 🍰 ✨
5 files reformatted, 2 files left unchanged.