Assessing quality of a Python code

Reading time5 min

En bref

Résumé de l’article

Dans l’article sur les bonnes pratiques de programmation, nous vous avons présenté des éléments à garder en tête lors de l’écriture de code.
Dans cet article, nous vous présentons deux outils qui peuvent vous aider à évaluer la qualité d’un code déjà écrit.

De plus, nous introduisons le “type hinting” (annotations de type), qui peut vous aider à être plus précis sur le type de variable attendu à différents endroits dans votre code.
C’est une fonctionnalité optionnelle en Python, qui peut être exploitée par certains IDE pour prévenir les erreurs.

Points clés

  • Python est un langage à typage dynamique.

  • Le type hinting, surtout pour les fonctions, aide à améliorer la lisibilité du code.

  • Certains outils comme mypy peuvent exploiter les annotations de type pour éviter des bugs potentiels.

  • L’outil pylint peut attribuer une note à votre code.

Contenu de l’article

1 — Type hinting

Python est un langage à typage dynamique, ce qui signifie qu’il déduit le type d’une variable à partir de la valeur qui lui est assignée.
Bien que cela rende la programmation plus rapide, cela réduit la lisibilité du code.

Le Type Hinting a été introduit dans Python 3.5 pour permettre au programmeur de spécifier le type de la variable manipulée.
Ces indications purement informatives rendent le code plus facile à lire et à étendre.
Les types dits basiques sont automatiquement gérés, mais les types composés doivent être explicitement inclus.
Voici un exemple d’utilisation des annotations de type :

# 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  

Des bibliothèques additionnelles telles que typing_extensions ou numbers fournissent des types supplémentaires pour le type hinting.
Vous les rencontrerez fréquemment durant ce cours.

Information

Contrairement à Python, chaque type doit être explicite en Java, donc le type hinting n’est pas nécessaire.

Certains outils peuvent être utilisés pour exploiter les annotations de type et améliorer les performances de débogage.
Par exemple, la bibliothèque mypy utilise les annotations de type pour détecter les erreurs de typage, comme nous le verrons ensuite.

Information

En pratique, l’usage courant du type hinting est de l’utiliser pour les arguments de fonction et les types de retour (pour améliorer la documentation), mais pas pour toutes les variables que vous pouvez définir (bien que vous puissiez le faire).
Par exemple, vous devriez faire :

# 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  

Mais pas :

# 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 est un langage à typage dynamique, qui n’impose pas de typage explicite, contrairement à Java.
Ainsi, si le typage est extrêmement important dans votre cas d’application, vous pourriez préférer passer à un langage plus approprié plutôt que d’essayer de bricoler Python.
Sinon, il est préférable de suivre les pratiques de codage choisies pour le langage.

2 — Vérification de type avec MyPy

Les erreurs de type sont découvertes à l’exécution et sont parfois cachées dans des blocs conditionnels imbriqués.
mypy est un outil utile pour vérifier la conformité des types des arguments passés à votre fonction.
mypy exploite les annotations de type dans votre code pour vérifier la validité des valeurs passées en arguments des fonctions.

Considérons le code suivant, dans un fichier nommé 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))  

Lancer la commande suivante :

python -m mypy distances.py  
python -m mypy distances.py  
mypy distances.py  
mypy distances.py  

Conduit aux erreurs suivantes indiquant clairement des problèmes de type :

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 — Évaluez votre code avec Pylint

Des outils tels que Pylint peuvent vous aider à évaluer si votre code respecte la norme Python Enhancement Proposals (PEP) 8.
Par exemple, vous pouvez lancer la commande suivante, qui donnera une note à votre code.

python -m pylint my_code.py  
python -m pylint my_code.py  
pylint my_code.py  
pylint my_code.py  

À titre d’exemple, considérez les codes Python suivants que nous avons déjà rencontrés dans le cours sur les bonnes pratiques de programmation :

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)  

Lancer pylint sur ces codes nous donne les sorties suivantes :

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)  

Pour avoir un retour direct dans votre IDE (VSCode) concernant la conformité de votre code par rapport à la PEP8, vous pouvez installer l’extension pylint pour VSCode.

Pour aller plus loin

On dirait que cette section est vide !

Y a-t-il quelque chose que vous auriez aimé voir ici ?
Faites-le nous savoir sur le serveur Discord !
Peut-être pouvons-nous l’ajouter rapidement.
Sinon, cela nous aidera à améliorer le cours pour l’année prochaine !

Pour aller au-delà