Pylint is a high-level Python style enforcer. While flake8 and black will take care of "local" style: where the newlines occur, how comments are formatted, or find issues like commented out code or bad practices in log formatting.
Pylint is extremely aggressive by default. It will offer strong opinions on everything from checking if declared interfaces are actually implemented to opportunities to refactor duplicate code, which can be a lot to a new user. One way of introducing it gently to a project, or a team, is to start by turning all checkers off, and then enabling checkers one by one. This is especially useful if you already use flake8, black, and mypy: Pylint has quite a few checkers that overlap in functionality.
However, one of the things unique to Pylint is the ability to enforce higher-level issues: for example, number of lines in a function, or number of methods in a class.
These numbers might be different from project to project and can depend on the development team's preferences. However, once the team comes to an agreement about the parameters, it is useful to enforce those parameters using an automated tool. This is where Pylint shines.
Configuring Pylint
In order to start with an empty configuration, start your .pylintrc
with
[MESSAGES CONTROL]
disable=all
This disables all Pylint messages. Since many of them are redundant, this makes sense. In Pylint, a message
is a specific kind of warning.
You can check that all messages have been turned off by running pylint
:
$ pylint <my package>
In general, it is not a great idea to add parameters to the pylint
command-line: the best place to configure your pylint
is the .pylintrc
. In order to have it do something useful, we need to enable some messages.
In order to enable messages, add to your .pylintrc
, under the [MESSAGES CONTROL]
.
enable=<message>,
...
For the "messages" (what Pylint calls different kinds of warnings) that look useful. Some of my favorites include too-many-lines
, too-many-arguments
, and too-many-branches
. All of those limit complexity of modules or functions, and serve as an objective check, without a human nitpicker needed, for code complexity measurement.
A checker is a source of messages: every message belongs to exactly one checker. Many of the most useful messages are under the design checker. The default numbers are usually good, but tweaking the maximums is straightfoward: we can add a section called DESIGN
in the .pylintrc
.
[DESIGN]
max-args=7
max-locals=15
Another good source of useful messages is the refactoring
checker. Some of my favorite messages to enable there are consider-using-dict-comprehension
, stop-iteration-return
(which looks for generators which use raise StopIteration
when return
is the correct way to stop the iteration). and chained-comparison
, which will suggest using syntax like 1 <= x < 5
rather than the less obvious 1 <= x && x > 5
Finally, an expensive checker, in terms of performance, but highly useful, is similarities
. It is designed to enforce "Don't Repeat Yourself" (the DRY principle) by explicitly looking for copy-paste between different parts of the code. It only has one message to enable: duplicate-code
. The default "minimum similarity lines" is set to 4
. It is possible to set it to a different value using the .pylintrc
.
[SIMILARITIES]
min-similarity-lines=3
Pylint makes code reviews easy
If you are sick of code reviews where you point out that a class is too complicated, or that two different functions are basically the same, add Pylint to your Continuous Integration configuration, and only have the arguments about complexity guidelines for your project once.
1 Comment