avatarYanick Andrade

Summary

The article discusses the importance of code formatting in Python, comparing two popular formatters: Black and Ruff, and provides guidance on how to use them to maintain clean, readable, and PEP 8 compliant code.

Abstract

The Python programming language benefits from well-formatted code, which enhances readability and adherence to PEP 8 standards. The article introduces Black, a widely-adopted Python code formatter known for its ease of use and compliance with PEP 8. It also presents Ruff, a newer formatter written in Rust, which offers significantly faster performance and compatibility with Black, along with additional customization options. The author emphasizes the importance of code formatting tools in automating the adherence to coding standards, thereby allowing developers to focus on functionality. The article guides readers through installing and using both Black and Ruff, demonstrating their capabilities to format code horizontally and vertically, and discusses the benefits of integrating these tools into the development workflow, such as using them with pre-commit hooks.

Opinions

  • The author expresses a strong preference for well-formatted code, viewing it as a reflection of the programmer's professionalism.
  • Black is highly regarded by the author's team, being their default formatter due to its simplicity and effectiveness in enforcing PEP 8.
  • The author is intrigued by Ruff's performance and features, considering it a viable alternative to Black and potentially suitable for future projects.
  • There is an appreciation for Ruff's compatibility with Black, making it an easy transition for those looking to switch formatters.
  • The author values the ability to customize formatting preferences, such as choosing between single and double quotes, which is a feature offered by Ruff.
  • The article suggests that formatting code is not just a good practice but a habit that all programmers should adopt, as code is read more often than it is written.

Python Code Formatter — Black or Ruff?

Understand how code formatting works in Python

Photo by Max Kleinen on Unsplash

Not a member yet? Read it for free here!

If there’s one thing I like, is well-formatted code. It’s like staring at a work of art.

A well-formatted code says a lot about the person who coded it. And when I see one I automatically admire and respect the programmer(s) responsible for it.

On the other hand, when I see a poorly formatted code, I cannot help but feel weird and wonder how someone can format a code and leave it like that.

I mean, “As long as it’s working, who cares?”, right?

Wrong.

Thankfully, we can format our Python code without much effort. There are plenty of tools out there that can do this job for us.

We will cover three popular Python code formatter that you can choose from to help you maintain your code in order.

By the end of this article you will be able to:

  • Understand why it is important to keep your code well-formatted
  • Set up and use a Python formatter
  • Choose and apply the one you like the most in your project

Code Formatter

Formatting a code is a way of making sure that your code is well organized, readable, and that it follows the rules and guidelines of a said Programming Language.

Like Guido said, “The code is read much more often than it is written”. So making sure our code is pleasant to read is our duty.

In Python, we have PEP 8 as the Style Guide for Python code. If we want our code to adhere to the Python standard, we must follow it.

It provides guides on code lay-out; comments; naming conventions; and more.

In Python, we know that our code needs to be indented with 4 spaces per level:

def foo():
    # 4 spaces from the line start ✅
    print("4 spaces indentation level")


def bar():
   # 3 spaces from the line start ❌
   print("3 spaces indentation level. This won't work!")

We have rules and guidelines on how to better comment on our code using one of these possible comment :

— Block comments

When we want to comment on the line of code that follows or everything within a scope:

def foo():
    # Block comment about what this function is for
    print("4 spaces indentation level")

— Inline Comments

Inline comments are comments that are at the end of a statement. The comment guideline for this says that it should be separated from the statement with two spaces:

def foo():
    print("4 spaces indentation level")  # inline comment. print a message

Each of these guidelines that we covered here and the rest that we didn’t, if well applied, can make a difference in our project longevity.

The good thing is that you don’t need to worry about reading the PEP 8 and try to follow it line-by-line.

That’s why we have code formatters. These are tools that automatize this process for us in a convenient way.

In this article, we are going to talk about two major Python formatter — the OG of formatter (Black) and the new kid in town (Ruff).

So let’s see more about them.

Black Code Formatter

Black is considered “the OG of Python formatters”.

Black is the default formatter used by our team in every project we create. It helps us speed up the formatting so we can focus only on making sure that our code runs as expected.

It is PEP 8 compliant, which means that it follows the guidelines defined by Python.

Before we delve into it in more detail, let’s make sure you have it installed on your virtual environment:

pip install black # run this command to install black

Now let’s look at one example of poor-formatted code:

from my_lib import (
  module_a,
  module_b,
  module_c
)


def foo():
    

    print("Unecessary space above this print.")

To automatically format our code, we can run the black command. There are two ways of doing that a) by running black . which formats everything inside the current directory b) by running black your_module.py to format a single Python file.

black .  # format everything inside the current directory
from my_lib import module_a, module_b, module_c # make the import fit one line


def foo():
    
    print("Unecessary space above this print.") # remove extra white space leaving only one

Now let’s understand how Black manages code formatting horizontally and vertically, and how it decides how to format a line of code.

To format a horizontal line of code, Black does whatever needs to be done to comply with pycodestyle.

If you don’t know, pycodestyle is simply a tool that you can install and run to check if your code follows PEP 8 conventions.

pip install pycodestyle #  you can install it to run a test on our example

Let’s run pycodestyle on the following code snippet and see what we

# foo.py
def foo():


    print("Unecessary space above this print.")

As you can see, we have too many whitespaces between the function definition statement and the print statement. Which is against the PEP 8 convention.

pycodestyle --first foo.py #  run pycodestyle against our module foo.py
:> foo.py:4:5: E303 too many blank lines (2) # output

— Vertical formatting

Vertical formatting is applied to multiple imports; function definitions; function calling; lists, tuples, dict definitions; etc.

First, black tries to format/render a full expression per line. If it’s too long for the line, black breaks the line and puts the contents inside the brackets or parenthesis in a new line.

my_list = [
    0,
    1,
    2, 
    3,
    4, 
    5, 
    6, 
    7, 
    8, 
    9,
] # First, black tries to put this in a single line


my_list_formatted = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

If it is still too long for the line, which by default is 88 characters per line, then it breaks the line and puts each content in a single line separated by a comma.

# If the contents are still too long in a single line, break one line
my_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]

# break line starting in the first bracket
my_list_formatted = [
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
]

Another example would be with a function definition where we have multiple parameters:

# unformatted code
def foo[T](name: str, age: int, email: str, birth_date: str, country: str, city: str, gender: str) -> T:
    # do something
    ...

# black formatted code
def foo[T](
    name: str, 
    age: int, 
    email: str, 
    birth_date: str, 
    country: str, 
    city: str, 
    gender: str,
) -> T:

All this can be simply done by running the black command like we saw before.

Given Black’s popularity as the OG of Python formatters, you might wonder why we should explore other alternatives.

So let’s take a look at another color — purple.

Ruff Code Formatter

A Python code formatter written in… Rust!

Because Ruff is written in Rust, it is faster than Black, I mean, really fast. 30x faster.

Screenshot from Ruff’s site taken by the Author

If you have a large project that you never formatted and you decided that now might be a good time to format it to be compliant with PEP 8, Ruff’s performance might be very handy.

If you have been using Black in your project as the default code formatter, no problem. Ruff is compatible with it. It was designed to be a “drop-in replacement” for Black.

One thing that I like a lot about Ruff is that it is possible to configure the desired quote (single quote or double quote). Black uses double quotes so far it does not have any configuration to switch to a single quote.

# Black formatter
name = "Jane Doe"

# Ruff formatter
name = 'Jane Doe'

Despite being super new to the market, Ruff has been gaining popularity among Python programmers and has now over 27K stars on GitHub.

So let’s give it a try and see how it works:

pip install ruff  # install ruff in your virtual environment

Bear in mind that Ruff is only compatible with Python 3.12 and higher. So if you’re using a lower version of Python make sure you upgrade it. If you need help upgrading your Python version, take a look at this article I wrote.

Let’s try Ruff in this code:

# foo.py
import os, sys

my_list = [
0,
1,
2, 
3,
4, 
5, 
6, 
7, 
8, 
9,
]

def foo():

    print("Unecessary space above this print.")

The first thing we are going to try is to run it as we did with pycodestyle to see what is wrong with our code:

ruff check  # lint all files in the current directory

# output
foo.py:1:1: E401 [*] Multiple imports on one line
foo.py:1:8: F401 [*] `os` imported but unused
foo.py:1:12: F401 [*] `sys` imported but unused
Found 3 errors.

If we want to check only a specific file or a specific directory, we can do it by adding the path in the command:

ruff check path/to/code # run ruff in a specific directory
ruff check foo.py # run ruff only on foo.py module

To format our code using Ruff we run the following command:

ruff format
import os, sys

my_list = [
    0,
    1,
    2,
    3,
    4,
    5,
    6,
    7,
    8,
    9,
]


def foo():
    print("Unecessary space above this print.")

See that it complained about multiple imports in one line — import os, sys. In this case, both Black and Ruff can do nothing about this kind of situation (yet).

If you want to configure your formatter to ignore certain files or directories, to use a single quote instead of a double, the line length to consider, etc. You can do it inside a ruff.toml file placed in your project root directory.

# ruff.toml
exclude = ["venv"] # everything you wish to ignore, similar to gitignore

line-length = 88
indent-width = 4

[format]
quote-style = "single" # Use a single quote instead of double

If you want to take things to the next level, you can configure Ruff to check your code every time you commit using it with the pre-commit framework (we will discuss this framework in the next article in depth).

First, install pre-commit and then we will configure it:

pip install pre-commit

Create a .pre-commit-config.yaml file in your project root directory and paste the following configuration:

# .pre-commit-config.yaml
repos:
-   repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.4.4
    hooks:
    -   id: ruff-format

You could also do the same thing for Black in case you decide to stick with it:

#  .pre-commit-config.yaml
repos:
-   repo: https://github.com/psf/black
    rev: v24.4.2
    hooks:
    -   id: black

Final Thoughts

Formatting your code is not only a good idea, it is a good habit that every programmer should adopt. Remember, we don’t code for us.

I’ve been using Black for almost 3 to 4 years and I like it. It does the job pretty well. But I have to admit that Ruff seems interesting and I might discuss it with my team and try to use it in some of our projects.

Do you use any format tool in your Python projects? If so, which is your favorite?

Hi! Thank you for your time reading my article. If you enjoyed this article and would like to receive similar content directly to your inbox.

In Plain English 🚀

Thank you for being a part of the In Plain English community! Before you go:

Python
Programming
Data Science
Technology
Machine Learning
Recommended from ReadMedium