Python code analysis can be a heavy subject, but it can be very helpful in making your programs better. There are several Python code analyzers that you can use to check your code and see if they conform to standards. pylint is probably the most popular. It’s very configurable, customizable and pluggable too. It also checks your code to see if it conforms to PEP8, the official style guide of Python Core and it looks for programming errors too. We’re going to spend a few minutes looking at some of the things you can do with this handy tool.
Getting Started
Sadly, pylint isn’t included with Python, so you’ll need to go out and download it from Logilab or PyPI. If you have pip installed, then you can install it like this:
pip install pylint
Now you should have pylint installed along with all its dependencies. Now we are ready to roll!
Analyzing Your Code
The latest version as of this writing is 0.25.1. Once pylint is installed, you can run it on the command line without any arguments to see what options it accepts. Now we need some code to test with. Since I wrote some crummy code for my PyChecker article last year, we’ll re-use that here and see if pylint picks up the same problems. There should be four issues. Here’s the code:
import sys ######################################################################## class CarClass: """""" #---------------------------------------------------------------------- def __init__(self, color, make, model, year): """Constructor""" self.color = color self.make = make self.model = model self.year = year if "Windows" in platform.platform(): print "You're using Windows!" self.weight = self.getWeight(1, 2, 3) #---------------------------------------------------------------------- def getWeight(this): """""" return "2000 lbs"
Now let’s run pylint against this code and see what it finds. You should get something like the following:
C:\Users\mdriscoll\Desktop>pylint crummy_code.py
No config file found, using default configuration
************* Module crummy_code
C: 1,0: Missing docstring
C: 4,0:CarClass: Empty docstring
E: 15,24:CarClass.__init__: Undefined variable 'platform'
E: 18,22:CarClass.__init__: Too many positional arguments for function call
E: 21,4:CarClass.getWeight: Method should have "self" as first argument
C: 21,4:CarClass.getWeight: Invalid name "getWeight" (should match [a-z_][a-z0-9
_]{2,30}$)
C: 21,4:CarClass.getWeight: Empty docstring
R: 21,4:CarClass.getWeight: Method could be a function
R: 4,0:CarClass: Too few public methods (1/2)
W: 1,0: Unused import sys
Report
======
13 statements analysed.
Messages by category
--------------------
+-----------+-------+---------+-----------+
|type |number |previous |difference |
+===========+=======+=========+===========+
|convention |4 |NC |NC |
+-----------+-------+---------+-----------+
|refactor |2 |NC |NC |
+-----------+-------+---------+-----------+
|warning |1 |NC |NC |
+-----------+-------+---------+-----------+
|error |3 |NC |NC |
+-----------+-------+---------+-----------+
Messages
--------
+-----------+------------+
|message id |occurrences |
+===========+============+
|C0112 |2 |
+-----------+------------+
|W0611 |1 |
+-----------+------------+
|R0903 |1 |
+-----------+------------+
|R0201 |1 |
+-----------+------------+
|E1121 |1 |
+-----------+------------+
|E0602 |1 |
+-----------+------------+
|E0213 |1 |
+-----------+------------+
|C0111 |1 |
+-----------+------------+
|C0103 |1 |
+-----------+------------+
Global evaluation
-----------------
Your code has been rated at -6.92/10
Statistics by type
------------------
+---------+-------+-----------+-----------+------------+---------+
|type |number |old number |difference |%documented |%badname |
+=========+=======+===========+===========+============+=========+
|module |1 |NC |NC |0.00 |0.00 |
+---------+-------+-----------+-----------+------------+---------+
|class |1 |NC |NC |0.00 |0.00 |
+---------+-------+-----------+-----------+------------+---------+
|method |2 |NC |NC |50.00 |50.00 |
+---------+-------+-----------+-----------+------------+---------+
|function |0 |NC |NC |0 |0 |
+---------+-------+-----------+-----------+------------+---------+
Raw metrics
-----------
+----------+-------+------+---------+-----------+
|type |number |% |previous |difference |
+==========+=======+======+=========+===========+
|code |12 |63.16 |NC |NC |
+----------+-------+------+---------+-----------+
|docstring |4 |21.05 |NC |NC |
+----------+-------+------+---------+-----------+
|comment |3 |15.79 |NC |NC |
+----------+-------+------+---------+-----------+
|empty |0 |0.00 |NC |NC |
+----------+-------+------+---------+-----------+
Duplication
-----------
+-------------------------+------+---------+-----------+
| |now |previous |difference |
+=========================+======+=========+===========+
|nb duplicated lines |0 |NC |NC |
+-------------------------+------+---------+-----------+
|percent duplicated lines |0.000 |NC |NC |
+-------------------------+------+---------+-----------+
If you want to know what those items in the Messages section mean with their cryptic ids, you can make pylint tell you by doing this on the command line:
pylint --help-msg=C0112
However, we really only care about the first section of the report as the rest is basically just tables showing the same information in a more obscure manner. Let’s take a look at that section more closely:
C: 1,0: Missing docstring
C: 4,0:CarClass: Empty docstring
E: 15,24:CarClass.__init__: Undefined variable 'platform'
E: 18,22:CarClass.__init__: Too many positional arguments for function call
E: 21,4:CarClass.getWeight: Method should have "self" as first argument
C: 21,4:CarClass.getWeight: Invalid name "getWeight" (should match [a-z_][a-z0-9
_]{2,30}$)
C: 21,4:CarClass.getWeight: Empty docstring
R: 21,4:CarClass.getWeight: Method could be a function
R: 4,0:CarClass: Too few public methods (1/2)
W: 1,0: Unused import sys
First we need to figure out what the letters designate: C is for convention, R is refactor, W is warning and E is error. pylint found 3 errors, 4 convention issues, 2 lines that might be worth refactoring and 1 warning. The 3 errors plus the warning were what I was looking for. We should try to make this crummy code better and reduce the number of issues. We’ll fix the imports and change the getWeight function to get_weight since camelCase isn’t allowed for method names. We also need to fix the call to get_weight so it passes the right number of arguments and fix it so it has “self” as the first argument. Here’s the new code:
# crummy_code_fixed.py import platform ######################################################################## class CarClass: """""" #---------------------------------------------------------------------- def __init__(self, color, make, model, year): """Constructor""" self.color = color self.make = make self.model = model self.year = year if "Windows" in platform.platform(): print "You're using Windows!" self.weight = self.get_weight(3) #---------------------------------------------------------------------- def get_weight(self, this): """""" return "2000 lbs"
Let’s run this against pylint and see how much we’ve improved the results. For brevity, we’ll just show the first section below:
C:\Users\mdriscoll\Desktop>pylint crummy_code_fixed.py
No config file found, using default configuration
************* Module crummy_code_fixed
C: 1,0: Missing docstring
C: 4,0:CarClass: Empty docstring
C: 21,4:CarClass.get_weight: Empty docstring
W: 21,25:CarClass.get_weight: Unused argument 'this'
R: 21,4:CarClass.get_weight: Method could be a function
R: 4,0:CarClass: Too few public methods (1/2)
That helped a lot! If we added docstrings, we could halve the number of issues.
Wrapping Up
The next step would be to try running pylint against some of your own code or against a Python package like SQLAlchemy and seeing what gets output. You can learn a lot about your own code using this tool. If you have Wingware’s Python IDE, you can install pylint as a tool that you can run with just a couple mouse clicks. You may find some of the warnings to be annoying or not even really applicable. There are ways to suppress such things as deprecation warnings through command line options. Or you can use the –generate-rcfile to create an example config file that will help you control pylint. Note that pylint does not import your code, so you don’t have to worry about undesirable side effects.
At this point you should be ready to start improving your own code base. Go forth and make your code amazing!
Further Reading
- PyLint official website
- My article on PyChecker
- PyChecker’s official website (a similar project to PyLint)
- pyflakes – another similar project
- Doug Hellman’s review of Python Static Code Analyzers from the Python Magazine
> It also checks your code to see if it conforms to PEP8
No, it does not – see http://stackoverflow.com/questions/6879640/
Even that link says that PyLint implements checks for most of PEP8.
Pingback: Visto nel Web – 31 « Ok, panico
First of all, thanks for the write ups on pylint and friends.
Just started to give pylint another try and looking for ways of ‘reducing’ warning and info messages for things like
def onSomeEventHandler(self, event):
Above will generate a warning for unused param “event”. Is there a nice way to get rid of this warning for all the event handlers?
I know I could:
– stick “W0613” into the pylintrc file
– or add “#pylint: disable=W0613” to the above line
Don’t like 1 as it could hide warnings I might like to know and 2 is a p… in …, isn’t there a way to teach pylint that “self, event” is fine as param?
@Werner – those appear to be the top two ways to do what you want. I also found this article on Stack (http://stackoverflow.com/q/10107350/393194) where it mentions some kind of dummy variable argument you can set up. It’s not very clear if you can actually edit it or if you’re stuck with the special naming infrastructure that’s already builtin.
Your writings on Python 101 are really helpful. Thanks. Keep writing!
Pingback: Confluence: Analytics Environment
Pingback: Intro to Black - The Uncompromising Python Code Formatter - The Mouse Vs. The Python
Pingback: An Intro to Flake8 - The Mouse Vs. The Python
Pingback: Intro to Black - The Uncompromising Python Code Formatter - Mouse Vs Python