2018, Aug 13 - Dimitri Merejkowsky
License: CC By 4.0

Introduction

In my last article[1] I explained why I decided to no longer use pylint.

1: Bye-bye pylint

In a nutshell, most pylint warning are now caught by other linters, and all that's left are some warnings.

In an other article[2] I mentioned flake8[3] briefly, saying that I preferred a simple bash script to drive the execution of the various linters.

2: How I Lint My Python

3: http://flake8.pycqa.org/en/latest/

I was concerned that flake8 did not include pylint (now I no longer care of course). Also, I did not like the fact that flake8 forces a specific version for all the linters it runs.

Well, I was wrong (again).

flake8 is awesome

First off, I thought flake8 only combined the pyflakes and pycodestyle linters and nothing else. . Well, it also includes mccabe[4] by default.

4: https://pypi.org/project/mccabe/

Also, as explained in the FAQ[5], there are significant advantages in having the versions of the linters frozen this way.

5: http://flake8.pycqa.org/en/latest/faq.html#why-does-flake8-use-ranges-for-its-dependencies

Here are some other features I overlooked:

A nice surprise

The last time I upgraded pylint, I only got one new warning. It was on a line looking like this:

my_set = set([elem for elem in my_list if some_condition(elem)])

The intent here is to build a unique set from a list of elements that satisfy a given condition.

pylint emitted the following warning:

R1718: Consider using a set comprehension (consider-using-set-comprehension)

Indeed, the code can also be written like this, using a *set comprehension* instead:

my_set = {elem for elem in my_list if some_condition(elem)}

Advantages:

Well, there is already a flake8 plugin called flake8-comprehension[6] that deals with these kind of issues.

6: https://pypi.org/project/flake8-comprehensions/

In fact, there are a bunch of flake8 plugins[7] available!

7: https://pypi.org/search/?q=flake8-

Plus, adding a new flake8 plugin is as easy as running `pipenv install --dev <plugin name>` and nothing else has to change :)

The future is bright

Itamar Turner-Trauring , in the comment section[8] on dev.to gave an interesting example:

8: https://dev.to/dmerejkowsky/bye-bye-pylint-4chh

# Note: I've taken the liberty of making the code a bit less abstract
def greet(prefix, name):
    print(prefix, name)

greeters = list()
prefixes = ["Hi", "Hello", "Howdy"]
for prefix in prefixes:
    greeters.append(lambda x: greet(prefix, x))

for greeter in greeters:
    greeter("world")

You may think the following code would print:

Hi world
Hello world
Howdy world

but instead it prints:

Howdy world
Howdy world
Howdy world

This has to do with how the closures work in Python, and the bug is indeed caught by pylint:

$ pylint example.py
 W0640: Cell variable `prefix` defined in loop (cell-var-from-loop)

I did not find a flake8 plugin that could catch this bug right away, but I found flake8-bugbear[9], a plugin to "find likely bugs and design problems".

9: https://github.com/PyCQA/flake8-bugbear

The plugin is well-written, well-tested and easy to contribute to.

I've already tried porting some pylint warnings to flake8-bugbear[10] and so far it has been much easier than I thought.

10: https://github.com/PyCQA/flake8-bugbear/pull/51

Therefore, next time I find a bug that could have been caught by inspecting the AST (which is what both flake8-bugbear and pylint do), I know how to write or contribute to a flake8 plugin in order to automatically catch it during CI.

Thus, I can slowly build a complete replacement for pylint, with just the warnings I care about, and without the configuration issues and false positives.

Bright future indeed!

----

Back to Index

Contact me