Skip to content

mramshaw/Python-Linters

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

18 Commits
 
 
 
 
 
 

Repository files navigation

Python Linters

It's probably a best practice to use linters, especially with interpreted languages. By which I mean javascript, node.js, PHP or Python (and probably others).

If you are a beginner linters will generally catch any n00b errors - which will probably save everyone some time and/or embarrassment. And for most people they will often catch potential problems that were not spotted. For anyone who frequently uses different languages, they suggest a style and idiom appropriate for the language. Often these seem to be very minor things - but they may have specific meanings in any particular language. For instance, whether or not a variable or function is capitalised can be significant - but often it will simply be such useful suggestions as using underscores instead of hyphens, and whether or not camel-case is appropriate.

All in all, it's generally a best practice to keep code as idiomatic as possible. And linters are great for this.

Here I am trying out pycodestyle, pylint, pydocstyle, and Black.

pycodestyle and pylint are code linters whereas pydocstyle is a documentation linter.

Black, on the other hand, is a code formatter (along the lines of gofmt). These can reduce the pain of code reviews as there are fewer arguments about the proper way of formatting code to ensure readability. Also, one of Black's claims is that:

Black makes code review faster by producing the smallest diffs possible.

[Which sounds pretty useful.]

Contents

The contents are as follows:

pycodestyle

This is probably the linter to use, specifically for the PEP 8 integration.

In fact, it was formerly called pep8.

However, I still like pylint quite a lot, for one thing it will complain if a file is not named in a Pythonic way. And they've gamified pylint pretty well, it's oddly satisfying to clean up formatting and see the code score improve.

Installing pycodestyle

Run the following command:

$ pip3 install --user pycodestyle

Verify pycodestyle version

Verify the version as follows:

$ pycodestyle --version

Running pycodestyle

Run pycodestyle as follows:

$ pycodestyle xxxxx.py

Limit pycodestyle to the first occurrence of each linting error:

$ pycodestyle --first xxxxx.py

Show the offending source code and the relevant text from PEP 8:

$ pycodestyle --show-source --show-pep8 xxxxx.py

pylint

I'm kind of a sucker for UML and pylint ships with an interesting-looking tool (pyreverse) which I wanted to try out.

For anyone preparing to migrate code from Python 2 to Python 3, pylint will highlight potential problem areas.

Installing pylint

Run the following command:

$ pip3 install --user pylint

Verify pylint version

Verify the version as follows:

$ pylint --version

Running pylint

Run pylint as follows:

$ pylint xxxxx.py

For any obsessive-compulsives, pylint provides a code score (and monitors progress).

Ignore certain linting rules as follows:

$ pylint --disable=C0103 bad-python.py

In the example above, we are ignoring snake_case naming style - say to conform to benchmark or pytest coding conventions. We could also achieve the same result by annotating all of our offending code blocks as follows:

#!/usr/bin/env python3

# pylint: disable=C0103

"""
An example of Python code that will fail linting.
"""

pylint has some nice features - it is possible to disable a rule by it's name as well as by it's key. And it is also possible to disable a rule (or rules) for a single line of code.

For example, pylint will object to the following line of Django code:

from . import views

If this line of code is annotated as follows, pylint will be happy:

from . import views  # pylint: disable=relative-beyond-top-level

Note that we did not have to look up the key value for relative-beyond-top-level.

pyreverse

Run pyreverse as follows:

$ pyreverse -f PUB_ONLY -o png -p <package> *.py

[The default output format is dot, here we are specifying the graphic format png.]

For cpuinfo this looks as follows:

$ cd ~/.local/lib/python3.6/site-packages/cpuinfo
$ pyreverse -f PUB_ONLY -o png -p cpuinfo *.py
parsing cpuinfo.py...
parsing __init__.py...
parsing __main__.py...
$

The results look as follows:

cpuinfo package

And:

cpuinfo classes

An example of using pylint and pycodestyle

Here we will use a quick test case:

$ python3 bad-python.py
This code does not follow 'pylint' file naming conventions!
This code does not follow 'pycodestyle' coding conventions!
$

These linters flag different things, so it's probably worth running both:

$ pycodestyle *.py
bad-python.py:10:80: E501 line too long (85 > 79 characters)
bad-python.py:12:1: W391 blank line at end of file
$

And:

$ pylint *.py
************* Module bad-python
bad-python.py:12:0: C0305: Trailing newlines (trailing-newlines)
bad-python.py:1:0: C0103: Module name "bad-python" doesn't conform to snake_case naming style (invalid-name)
bad-python.py:7:0: W0105: String statement has no effect (pointless-string-statement)
bad-python.py:10:0: W0105: String statement has no effect (pointless-string-statement)

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

$

Or (disabling checks for snake_case naming):

$ pylint --disable=C0103 *.py
************* Module bad-python
bad-python.py:12:0: C0305: Trailing newlines (trailing-newlines)
bad-python.py:7:0: W0105: String statement has no effect (pointless-string-statement)
bad-python.py:10:0: W0105: String statement has no effect (pointless-string-statement)

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

$

[Note that the code score has increased.]

pydocstyle

While not strictly a linter, having well-formed docstrings allows for the use of automated documentation generators.

Installing pydocstyle

Run the following command:

$ pip install --user pydocstyle

Verify pydocstyle version

Verify the version as follows:

$ pydocstyle --version

Running pydocstyle

Run pydocstyle as follows:

$ pydocstyle xxxxx.py

There are a number of configurable options. For instance, it's possible to list PEP257 conventions to ignore as follows:

$ pydocstyle xxxxx.py --ignore=D210,D213,D401

A full list of pydocstyle errors may be found here.

[My personal feeling is that pydocstyle should be run from time to time, but is probably a bit too finicky for regular use. Your mileage may vary.]

An example of using pydocstyle

Again, we will use our test case:

$ pydocstyle *.py
bad-python.py:3 at module level:
        D200: One-line docstring should fit on one line with quotes (found 3)
$

Useful for anyone not too familiar with what pythonic means!

Black

While not strictly a linter, having well-formatted code will save everyone a lot of time.

Python 3.6

Note that black requires Python 3.6 or greater. Verify the version of python as follows:

$ python3 -V
...
$

If this is less than 3.6 there is always Dockerized python:

$ docker pull python:3.6
3.6: Pulling from library/python
c7b7d16361e0: Pull complete
b7a128769df1: Pull complete
1128949d0793: Pull complete
667692510b70: Pull complete
bed4ecf88e6a: Pull complete
94d1c1cbf347: Pull complete
f59f6b55cd0f: Pull complete
6513a2441bbb: Pull complete
792e28117005: Pull complete
Digest: sha256:52f872eae9755743c9494e0e3cf02a47d34b42032cab1e5ab777b30c3665d5f1
Status: Downloaded newer image for python:3.6
docker.io/library/python:3.6
$

Installing Black

Run the following command:

$ pip3 install --user black

If using docker, this looks as follows:

$ docker run -v $(pwd):/app -w /app --rm -it python:3.6 /bin/bash
root@7ebc68fc6b42:/app# pip install black
Collecting black
  Downloading https://files.pythonhosted.org/packages/fd/bb/ad34bbc93d1bea3de086d7c59e528d4a503ac8fe318bd1fa48605584c3d2/black-19.10b0-py36-none-any.whl (97kB)
     |████████████████████████████████| 102kB 2.0MB/s
Collecting toml>=0.9.4
  Downloading https://files.pythonhosted.org/packages/a2/12/ced7105d2de62fa7c8fb5fce92cc4ce66b57c95fb875e9318dba7f8c5db0/toml-0.10.0-py2.py3-none-any.whl
Collecting attrs>=18.1.0
  Downloading https://files.pythonhosted.org/packages/a2/db/4313ab3be961f7a763066401fb77f7748373b6094076ae2bda2806988af6/attrs-19.3.0-py2.py3-none-any.whl
Collecting regex
  Downloading https://files.pythonhosted.org/packages/e3/8e/cbf2295643d7265e7883326fb4654e643bfc93b3a8a8274d8010a39d8804/regex-2019.11.1-cp36-cp36m-manylinux1_x86_64.whl (643kB)
     |████████████████████████████████| 645kB 4.5MB/s
Collecting appdirs
  Downloading https://files.pythonhosted.org/packages/56/eb/810e700ed1349edde4cbdc1b2a21e28cdf115f9faf263f6bbf8447c1abf3/appdirs-1.4.3-py2.py3-none-any.whl
Collecting typed-ast>=1.4.0
  Downloading https://files.pythonhosted.org/packages/31/d3/9d1802c161626d0278bafb1ffb32f76b9d01e123881bbf9d91e8ccf28e18/typed_ast-1.4.0-cp36-cp36m-manylinux1_x86_64.whl (736kB)
     |████████████████████████████████| 737kB 6.0MB/s
Collecting click>=6.5
  Downloading https://files.pythonhosted.org/packages/fa/37/45185cb5abbc30d7257104c434fe0b07e5a195a6847506c074527aa599ec/Click-7.0-py2.py3-none-any.whl (81kB)
     |████████████████████████████████| 81kB 7.1MB/s
Collecting pathspec<1,>=0.6
  Downloading https://files.pythonhosted.org/packages/7a/68/5902e8cd7f7b17c5879982a3a3ee2ad0c3b92b80c79989a2d3e1ca8d29e1/pathspec-0.6.0.tar.gz
Building wheels for collected packages: pathspec
  Building wheel for pathspec (setup.py) ... done
  Created wheel for pathspec: filename=pathspec-0.6.0-cp36-none-any.whl size=26671 sha256=f19ceb5e9213d565a296cf859705ecf2998234a3c3b8e7799641121c601ad3ce
  Stored in directory: /root/.cache/pip/wheels/62/b8/e1/e2719465b5947c40cd85d613d6cb33449b86a1ca5a6c574269
Successfully built pathspec
Installing collected packages: toml, attrs, regex, appdirs, typed-ast, click, pathspec, black
Successfully installed appdirs-1.4.3 attrs-19.3.0 black-19.10b0 click-7.0 pathspec-0.6.0 regex-2019.11.1 toml-0.10.0 typed-ast-1.4.0
root@7ebc68fc6b42:/app#

Verify Black version

Verify the version as follows:

$ black --version

Running Black

Run Black as follows:

$ black .

Or, to see if black would make changes:

$ black . --check

Using docker, this looks as follows:

root@7ebc68fc6b42:/app# black . --check
would reformat bad-python.py
Oh no! 💥 💔 💥
1 file would be reformatted.
root@7ebc68fc6b42:/app#

Or, to simply view the changes black would make (it is still in beta, after all):

$ black . --diff

Using docker, this looks as follows:

root@325bb7a3d2a3:/app# black . --diff
--- bad-python.py	2018-12-14 18:12:44.889095 +0000
+++ bad-python.py	2019-11-04 14:03:13.113575 +0000
@@ -8,6 +8,5 @@
 print "This code does not follow 'pylint' file naming conventions!"
 
 """'pycodestyle' will complain about the blank line following the print statement."""
 print "This code does not follow 'pycodestyle' coding conventions!"
 
-
reformatted bad-python.py
All done! ✨ 🍰 ✨
1 file reformatted.
root@325bb7a3d2a3:/app#

[Note that black has removed the trailing blank line that pycodestyle was complaining about.]

To Do

  • Add notes on disabling specific pylint rules
  • Add notes on disabling specific pylint rules for a single line of code
  • Add notes on Black (code formatter)
  • Update for recent Python 3 (3.6.9)