Assessing quality of a Python code

Reading time5 min

In 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.

Information

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.

Information

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:

Output
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:

Output
************* 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

Important

The content of this section is optional. It contains additional material for you to consolidate your understanding of the current topic.

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

Important

The content of this section is very optional. We suggest you directions to explore if you wish to go deeper in the current topic.