Pylint: The Secret to Detecting Even Your Sneakiest Python Code Errors

Pylint is a tool that can help detect errors in your Python code. It's a program that you run against your codebase and it will print out messages describing any errors that it sees. Before we go too far into how Pylint works, let's take a look at a code sample that will illustrate an error that Pylint is good at finding, and one that is particularly difficult to notice without help.

Pylint is a tool that can help detect errors in your Python code. It's a program that you run against your codebase and it will print out messages describing any errors that it sees. Before we go too far into how Pylint works, let's take a look at a code sample that will illustrate an error that Pylint is good at finding, and one that is particularly difficult to notice without help. (You can also view the screencast).

Consider the following:

$ python innocent_code.py
This code is just fine
$ echo $?
0

The program appears to have run fine - it printed a message to the screen and exited with a return code of zero. Now, let's take a look at the code we actually ran:

import sys

message = "hi"

if "--print-message" in sys.argv:
print(mesage)

print("This code is just fine")

Look carefully at line 6. You'll notice that the message variable has been typo-d. If we had passed the "--print-message" argument to this program it would have tried to execute that block of code and died on line 6. Because Python is an interpreted and dynamically typed language, it's possible to have errors like this that aren't apparent just by running the program. Referencing an undefined variable is one example, importing a module that doesn't exist or having a syntax error are others.

$ python innocent_code.py --print-message
Traceback (most recent call last):
File "innocent_code.py", line 6, in
print(mesage)
NameError: name 'mesage' is not defined

This demonstrates that in Python it's possible to have code that is broken but appears to run fine for a long time, until you take a specific code path and uncover an issue. It's silly to have a problem that is in production because of a syntax error.

This is where Pylint comes in. Pylint is a static code analysis tool, meaning it analyzes your program without actually running it. It works by importing your module and inspecting the bytecode. It's compatible with both Python 2 and 3, and available on PyPi. Let's try running it against our code example:

$ pylint innocent_code
************* Module innocent_code
C: 1, 0: Missing module docstring (missing-docstring)
C: 3, 0: Invalid constant name "message" (invalid-name)
E: 6,10: Undefined variable 'mesage' (undefined-variable)

Report
======
6 statements analysed.

Statistics by type
------------------

+---------+-------+-----------+-----------+------------+---------+
|type     |number |old number |difference |%documented |%badname |
+=========+=======+===========+===========+============+=========+
|module   |1      |1          |=          |0.00        |0.00     |
+---------+-------+-----------+-----------+------------+---------+
|class    |0      |0          |=          |0           |0        |
+---------+-------+-----------+-----------+------------+---------+
|method   |0      |0          |=          |0           |0        |
+---------+-------+-----------+-----------+------------+---------+
|function |0      |0          |=          |0           |0        |
+---------+-------+-----------+-----------+------------+---------+

Raw metrics
-----------

+----------+-------+-------+---------+-----------+
|type      |number |%      |previous |difference |
+==========+=======+=======+=========+===========+
|code      |6      |100.00 |6        |=          |
+----------+-------+-------+---------+-----------+
|docstring |0      |0.00   |0        |=          |
+----------+-------+-------+---------+-----------+
|comment   |0      |0.00   |0        |=          |
+----------+-------+-------+---------+-----------+
|empty     |0      |0.00   |0        |=          |
+----------+-------+-------+---------+-----------+

Duplication
-----------

+-------------------------+------+---------+-----------+
|                         |now   |previous |difference |
+=========================+======+=========+===========+
|nb duplicated lines      |0     |0        |=          |
+-------------------------+------+---------+-----------+
|percent duplicated lines |0.000 |0.000    |=          |
+-------------------------+------+---------+-----------+

Messages by category
--------------------

+-----------+-------+---------+-----------+
|type       |number |previous |difference |
+===========+=======+=========+===========+
|convention |2      |2        |=          |
+-----------+-------+---------+-----------+
|refactor   |0      |0        |=          |
+-----------+-------+---------+-----------+
|warning    |0      |0        |=          |
+-----------+-------+---------+-----------+
|error      |1      |1        |=          |
+-----------+-------+---------+-----------+

Messages
--------

+-------------------+------------+
|message id         |occurrences |
+===================+============+
|undefined-variable |1           |
+-------------------+------------+
|missing-docstring  |1           |
+-------------------+------------+
|invalid-name       |1           |
+-------------------+------------+

Global evaluation
-----------------
Your code has been rated at -1.67/10 (previous run: -1.67/10, +0.00)

$ pylint -E innocent_code
************* Module innocent_code
E: 6,10: Undefined variable 'mesage' (undefined-variable)

At the very top you'll notice several messages, including one about our problem with trying to reference the "mesage" variable. You will also see two other messages which are warnings about style infractions. Pylint is capable of checking for PEP8 compliance and, by default, will warn you about this. The rest of the report is a bunch of statistics about your code, such as code makeup and rating of your code quality.

The big report in the output of Pylint can sometimes be too much information. Often when I'm running Pylint, I'm not concerned with things like code makeup or even style guidelines, I just want to see errors. Pylint has an argument for this, its the -E flag. Let's see what happens when we pass -E to Pylint:

$ pylint -E innocent_code
************* Module innocent_code
E: 6,10: Undefined variable 'mesage' (undefined-variable)

This truncates all the rest of the output and shows you just the errors you're seeking out. Pylint has tons of other options and we won't have time to cover them today, but you can find out about them in the Pylint documentation.

We run Pylint in our continuous integration environment, using the -E flag we just explored. All of our Python project build pipelines run Pylint, and if it finds any errors our code doesn't ship. This prevents me from ever having to explain to my boss that an application crashed because of a typo'd variable name (well, and automated tests and such, but that's another story). By default Pylint can only run against one module at a time, so to run pylint recursively against our code base we have to use the find command:

find . -name "*.py" -exec pylint -E '{}' +

This isn't optimal but it's an easy way to get the functionality we need.

At this point you know how to install and run Pylint against your codebase. It only takes a couple minutes, and if you're not using any other static code analysis tools at the moment I highly recommend it. Barring extensive automated tests, Pylint will probably find something you want to know about. This is a good way to convince someone (maybe even yourself) that Pylint is worth your time and effort. Pylint is also a good way to catch no-brainer errors in your code without having to take the time to run all your tests.

If you're interested in looking even deeper into Pylint, here are a few more resources to check out:

Note: There are several alternatives to Pylint, the most popular being Pyflakes. If you're curious about Pyflakes I would suggest giving it a look as well.

Here are a couple links for Pyflakes:



Close off Canvas Menu