Assessing quality of a Python code
Reading time5 minIn brief
Article summary
In the article on good programming practices, we have presented you things you should keep in mind when writing code. In this article, we present you two tools that can help you evaluate the quality of a code already written.
In addition, we introduce “type hinting”, which can help you be more precise on what variable type is expected where in your code. This is an optional feature in Python, that can be exploited by some IDEs to prevent errors.
Main takeaways
-
Python is a dynamically typed language.
-
Type hinting, especially for functions, help improving readability of the code.
-
Some tools such as
mypy
can exploit hinting to avoid potential bugs. -
The tool
pylint
can give a grade to your code.
Article contents
1 — Type hinting
Python is a dynamically typed language, which means that it derives the type of a variable from the value assigned to it. Although this makes coding faster, it reduces the readability of the code.
Type Hinting has been introduced in Python 3.5 to allow programmer specify the type of the manipulated variable. This purely informative indications make the code easier to read and extend. So called basic types are automatically handled, but composited types have to be explicitly included. Here is an example of usage of type hints:
# Import the List type
from typing import List
# A few examples of type hinting for variables
i : int = 0
ch : str = "!"
values : List[int] = [1, 2 , 3, 4]
# An example of type hinting for functions
# Here, we indicate types of arguments and of the returned value
def my_function (a: int, b: str) -> List[float]:
...
return 0.5 * a * b
Additional libraries such as typing_extensions
or numbers
provide additional types for hinting.
You will encounter them frequently during this course.
Contrary to Python, every type has to be explicit in Java, so type hinting is not needed.
Some tools can be used to exploit type hinting and improve debug performance.
For instance, the mypy
library uses type hints to detect typing errors, as we will see next.
In practice, the common usage of type hinting is to use it for function arguments and return types (to improve the documentation), but not for all variables you may define (although you can do it). As an example, you should do:
# Do not type every variable
i = 0
ch = "!"
values = [1, 2 , 3, 4]
# Still type functions
def my_function (a: int, b: str) -> List[float]:
...
return 0.5 * a * b
But not:
# This makes code less readable
i : int = 0
ch : str = "!"
values : List[int] = [1, 2 , 3, 4]
# Here, an external user wants to know how to use "my_function" properly
def my_function (a, b):
...
return 0.5 * a * b
Python is a dynamically typed language, that does not enforce explicit typing, contrary to Java. Thus, if typing is extremely important in your application case, you may want to swith to a more appropriate language rather than trying to patch Python. Otherwise, it is better to follow coding practices that were chosen for the language.
2 — Type checking using MyPy
Type errors are discovered at runtime and are sometimes hidden in nested conditional blocks.
mypy
is a useful tool to check the adequacy of the types of the arguments passed to yout function.
mypy
leverages the typing hints in your code to check the correctness of the values passed as arguments of functions.
Let us consider the following code, in a file named distances.py
.
# Needed imports
from typing import Tuple
# Let's define a function
def euclidean_distance (point_1 : Tuple[float, float], point_2 : Tuple[float, float]) -> float:
"""
Compute the Euclidean distance between two 2D points.
In:
* point_1: The first point.
* point_2: The second point.
Out:
* The Eucldiean distance between the points.
"""
# Compute distance
return ((point_1[0] - point_2[0])**2 + (point_1[1] - point_2[1])**2)**0.5
# Ask for user input
x : int = input("x-value of pt")
y : int = input("y-value of pt")
pt : Tuple[float, float] = (x, y)
print(euclidean_distance((0, 0), pt))
Running the following command:
python -m mypy distances.py
python -m mypy distances.py
mypy distances.py
mypy distances.py
Leads to the following errors clearly indicating type issues:
distances.py:22: error: Incompatible types in assignment (expression has type "str", variable has type "int")
distances.py:23: error: Incompatible types in assignment (expression has type "str", variable has type "int")
Found 2 errors in 1 file (checked 1 source file)
3 — Assess your code with Pylint
Tools such as Pylint can help you evaluate if your code complies to the Python Enhancement Proposals (PEP) 8 norm. For instance, you can run the following command, which will give a grade to your code.
python -m pylint my_code.py
python -m pylint my_code.py
pylint my_code.py
pylint my_code.py
As an example, consider the following Python codes we already met in the course on good programming practices:
m = input("Enter the message: ")
em = ""
i = 0
while (i <= len(m) - 1):
c = 1
ch = m[i]
j = i
while (j < len(m) - 1):
if (m[j] == m[j+1]):
c = c + 1
j = j + 1
else:
break
em = em + str(c) + ch
i = j + 1
print(em)
"""
This simple script encodes a message using run-length encoding.
The principle of this simple encoding is to count the number of times a character appears in a row and then append the count to the character.
Example: AAAZEEERRRR -> 3A1Z3E4R
"""
# Ask user for a string to work on
message = input("Enter the message to encode: ")
# Initialize variables
encoded_message = ""
i = 0
# Treat each character in the message
while i <= len(message) - 1:
# Count the number of times the character appears in a row
count = 1
ch = message[i]
j = i
while j < len(message) - 1 and message[j] == message[j+1]:
count = count + 1
j = j + 1
# Append the count and the character to the encoded message
encoded_message += str(count) + ch
i = j + 1
# Display the encoded message
print(encoded_message)
Running pylint
on these codes gives us the following outputs:
************* Module example
example.py:5:0: C0325: Unnecessary parens after 'while' keyword (superfluous-parens)
example.py:9:0: C0325: Unnecessary parens after 'while' keyword (superfluous-parens)
example.py:10:0: C0325: Unnecessary parens after 'if' keyword (superfluous-parens)
example.py:1:0: C0114: Missing module docstring (missing-module-docstring)
example.py:2:0: C0103: Constant name "em" doesn't conform to UPPER_CASE naming style (invalid-name)
example.py:6:4: C0103: Constant name "c" doesn't conform to UPPER_CASE naming style (invalid-name)
example.py:11:12: C0103: Constant name "c" doesn't conform to UPPER_CASE naming style (invalid-name)
------------------------------------------------------------------
Your code has been rated at 4.67/10 (previous run: 4.67/10, +0.00)
************* Module example
example.py:3:0: C0301: Line too long (144/100) (line-too-long)
example.py:11:0: C0103: Constant name "encoded_message" doesn't conform to UPPER_CASE naming style (invalid-name)
example.py:18:4: C0103: Constant name "count" doesn't conform to UPPER_CASE naming style (invalid-name)
example.py:22:8: C0103: Constant name "count" doesn't conform to UPPER_CASE naming style (invalid-name)
------------------------------------------------------------------
Your code has been rated at 6.92/10 (previous run: 5.38/10, +1.54)
To have direct feedbacks in your IDE (VSCode- about the conformity of your code wrt. the PEP8, you can install the pylint VSCode extension.
To go further
Looks like this section is empty!
Anything you would have liked to see here? Let us know on the Discord server! Maybe we can add it quickly. Otherwise, it will help us improve the course for next year!
To go beyond
-
The complete python PEP8
-
Mathematical techniques to help with implementation of program.
-
Mathematical tools to verify the correctness of a program.