Pratiques de codage en Python

Charles Gaillard

Charles Gaillard

Maxime Churin

Maxime Churin

quelqu'un qui met un pinceau sur une palette de couleurs

Dans le domaine du développement de logiciels, les développeurs travaillent généralement dans un environnement collaboratif en évolution rapide. Pour cette raison, il est fortement recommandé qu'ils suivent des directives et des pratiques de codage communes qui garantiront efficacité, clarté, détection facile des bogues et intégration. Après tout, un code correctement formaté et documenté a plus de chances d'être partagé et utilisé par l'ensemble de la communauté des développeurs

Nous proposons ici un ensemble d'outils de développement Python qui peuvent vous aider à atteindre un tel objectif, testés avec les versions de Python 3.8 :

  • noir
  • Flocon 8
  • Je sorte
  • Mypy
  • Style pydoc
  • Darglint

noir

noir est un outil de mise en forme rapide qui peut être utilisé très facilement en ligne de commande.

Le reformatage de votre code en noir vous permet de produire un code propre et lisible, ce qui facilite la détection des erreurs.

Dans cet exemple :

class Car:
  

   def __init__(self,
       brand: str ,  color: str  ) -> None:
       self.brand=   brand
       self.color=   color
   def __str__(self) -> str:
       return (
           f"Car(brand: {self.brand}, color: {self.color})"
       )

Course à pied :

black sample.py

Retours :

reformatted sample.py

All done! ✨ 🍰 ✨
1 file reformatted.

Et il transforme sample.py :

class Car:
  def __init__(self, brand: str, color: str) -> None:
      self.brand = brand
      self.color = color

  def __str__(self) -> str:
      return f"Car(brand: {self.brand}, color: {self.color})"

Configuration recommandée

[black]
line-length = 100
skip-magic-trailing-comma = true

Remarque: Skip-magic-trailing-comma : pour éviter de faire exploser une collection en un seul élément par ligne

Flocon 8

Flocon 8 est un outil qui enveloppe style pycode, pyflakes, et mccabe

  • Style Pycode: détecte toute erreur de formatage dans le code lié à la conformité PEP8. Vous trouverez ci-dessous une liste non exhaustive des erreurs :

E pour les codes d'erreur et W pour les avertissements

  • E1 : pour les erreurs d'indentation.
  • E2 : pour les erreurs d'espacement.
  • E7 : pour les erreurs de déclaration
  • W6 : pour les avertissements de dépréciation
  • Pyflakes: détecte toute incohérence dans le code. Vous trouverez ci-dessous une liste non exhaustive des erreurs :

F pour les codes d'erreur

  • F4 : pour les erreurs d'importation
  • F5 : pour les erreurs de format
  • F6 : pour les assignations incompatibles/les erreurs de comparaison
  • F7 : pour les erreurs de syntaxe
  • F8 : pour les erreurs liées aux variables
  • McCabe: détecte les erreurs de complexité.

Code d'erreur : C (erreur renvoyée toujours C901 pour violation de complexité)

Dans ce fichier sample.py :

import numpy as np
a=math.cos(math.pi)

Course à pied :

flake8 sample.py

Vous donne les erreurs suivantes :

sample.py:2:1: F401 'numpy as np' imported but unused
sample.py:3:2: E225 missing whitespace around operator
sample.py:3:3: F821 undefined name 'math'
sample.py:3:12: F821 undefined name 'math'
sample.py:3:20: W291 trailing whitespace

Configuration recommandée

[flake8]
max-line-length = 100
extend-ignore = E203, E501
ban-relative-imports = parents

Nous ignorons ces deux erreurs :

Nous avons également un ensemble de plugins flake8 que vous pouvez installer :

  • flake8-use-fstring : vérifiez % ou .format et suggérez d'utiliser des chaînes de f
  • Flake-8-print : vérifier les relevés imprimés
  • flake8-tidy-imports : écrire des importations plus ordonnées (interdit les importations depuis les modules parents et supérieurs, c'est-à-dire avec plus d'un.)

Je sorte

Je sorte est un outil qui fournit un utilitaire de ligne de commande qui trie vos importations (par ordre alphabétique et sépare les sections en importations standard, tierces, premières parties et enfin importations depuis le dossier local).

Dans cet exemple :

import numpy as np
import cv2
from a import b
from c import f, e, d
from typing import List, Tuple, Dict, Any
import os
import json
from .z import x, y
from .w import u, v

Course à pied :

isort sample.py

Transforme le fichier sample.py :

import json
import os
from typing import Any, Dict, List, Tuple

import cv2
import numpy as np
from a import b
from c import d, e, f

from .w import u, v
from .z import x, y

Configuration recommandée

[isort]
profile = "black"
line_length = 100

Nous devons définir le profil sur « noir » pour éviter de mauvaises interactions entre les deux outils.

Mypy

Mypy est un vérificateur de type statique. Vous devez saisir vos fonctions et variables pour l'utiliser. Vous pouvez configurer son niveau de rigueur si vous souhaitez toujours résoudre le type de manière dynamique dans certaines parties de votre code, mais n'oubliez pas que le fait d'avoir un projet typé de manière statique améliore la productivité et que la clarté ajoute des barrières de sécurité partout afin que vous puissiez détecter les erreurs avant même d'exécuter votre code.

Dans cet exemple :

from typing import List, Tuple

a: str = "abc"
b: int = 3

c = a + b

d: List[int] = []
d.append(a)
d.append((a, b))

e: Tuple[int, int]
e = (a, b)

def sample_function(x: int, y: int) -> Tuple[int, List[int], str]:
  pass

e = sample_function(a, b)

Course à pied :

mypy sample.py

Retours :

sample.py:6: error: Unsupported operand types for + ("str" and "int")
sample.py:9: error: Argument 1 to "append" of "list" has incompatible type "str"; expected "int"
sample.py:10: error: Argument 1 to "append" of "list" has incompatible type "Tuple[str, int]"; expected "int"
sample.py:13: error: Incompatible types in assignment (expression has type "Tuple[str, int]", variable has type "Tuple[int, int]")
sample.py:18: error: Incompatible types in assignment (expression has type "Tuple[int, List[int], str]", variable has type "Tuple[int, int]")
sample.py:18: error: Argument 1 to "sample_function" has incompatible type "str"; expected "int"
Found 6 errors in 1 file (checked 1 source file)

Configuration recommandée

[mypy]
python_version = "3.8"
exclude = ["tests"]
# --strict
disallow_any_generics = true
disallow_untyped_defs = true
no_implicit_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_return_any = true
implicit_reexport = false
strict_equality = true
# --strict end

Nous avons choisi de n'exiger la saisie que pour le code de production et non pour les tests.

Pour une nouvelle base de code, vous devez ajouter options strictes mais pour le code existant, vous devez ajouter les options mypy de manière itérative.

Style pydoc

Style pydoc est un outil qui vérifie la conformité de la plupart des PERSONNE 257 concernant vos docstrings. Docstring est un outil essentiel pour documenter votre code. Nous avons choisi d'adopter le Convention de style Google.

Il implémente différents groupes d'erreurs :

  • D1 : pour Docstrings manquantes
  • D2 : pour les problèmes d'espaces blancs
  • D3 : pour les problèmes de devis
  • D4 : pour les problèmes de contenu Docstring

Configuration recommandée

[pydocstyle]
convention = "google"

Dans l'échantillon non documenté précédent :

class Car:
  def __init__(self, brand: str, color: str) -> None:
      self.brand = brand
      self.color = color

  def __str__(self) -> str:
      return f"Car(brand: {self.brand}, color: {self.color})"

Course à pied :

pydocstyle sample.py

Retours :

sample.py:1 at module level:        D100: Missing docstring in public modulesample.py:1 in public class `Car`:        D101: Missing docstring in public classsample.py:2 in public method `__init__`:        D107: Missing docstring in __init__sample.py:6 in public method `__str__`:        D102: Missing docstring in public method

Nous aurions dû :

"""A one line summary of the module or program, terminated by a period."""


class Car:
    """Summary of class here.

    Longer class information...
    """

    def __init__(self, brand: str, color: str) -> None:
        """Inits Car with blah.
        Args:
            brand: A string with the car brand.
            color: A string with the car color.        """
        self.brand = brand
        self.color = color

    def __str__(self) -> str:
        """Performs operation blah."""
        return f"Car(brand: {self.brand}, color: {self.color})"

Darglint

Darglint est un outil permettant de vérifier que la docstring correspond à l'implémentation de la fonction ou de la méthode tout au long de son cycle de vie. Cela évite d'avoir une documentation obsolète lorsque la signature a changé. Il est préférable de l'utiliser en combinaison avec un vérificateur de style docstring tel que pydocstyle.

Il implémente différents groupes d'erreurs :

  • DAR0 : Syntaxe, mise en forme et style
  • DAR1 : Section artistique
  • DAR2 : Section des retours
  • DAR3 : Section des rendements
  • DAR4 : Section surélevé
  • DAR5 : Section des variables

Dans l'exemple documenté précédent :

"""A one line summary of the module or program, terminated by a period."""


class Car:
    """Summary of class here.

    Longer class information...
    """

    def __init__(self, brand: str, color: str) -> None:
        """Inits Car with blah.
        Args:
            brand: A string with the car brand.
            color: A string with the car color.        """
        self.brand = brand
        self.color = color

    def __str__(self) -> str:
        """Performs operation blah."""
        return f"Car(brand: {self.brand}, color: {self.color})"

Course à pied :

darglint sample.py

Retours :

sample.py:str:20: DAR201: - return

Nous aurions dû :

"""A one line summary of the module or program, terminated by a period."""


class Car:
    """Summary of class here.

    Longer class information...
    """

    def __init__(self, brand: str, color: str) -> None:
        """Inits Car with blah.

        Args:
            brand: A string with the car brand.
            color: A string with the car color.
        """
        self.brand = brand
        self.color = color

    def __str__(self) -> str:
        """Performs operation blah.

        Returns:
            str: object representation
        """
        return f"Car(brand: {self.brand}, color: {self.color})"

Intégration

Nous aimerions regrouper toutes ces configurations d'outils dans un seul fichier. Pour le stockage des configurations, nous vous recommandons d'utiliser .flake8 + pyproject.toml.

[flake8]
max-line-length = 100
extend-ignore = E203, E501
ban-relative-imports = parents
[tool.black]
line-length = 100
skip-magic-trailing-comma = true

[tool.isort]
profile = "black"
line_length = 100

[tool.mypy]
python_version = "3.8"
exclude = ["tests"]
# --strict
disallow_any_generics = true
disallow_untyped_defs = true
no_implicit_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_return_any = true
implicit_reexport = false
strict_equality = true
# --strict end

[tool.pydocstyle]
convention = "google"

Ensuite, si nous voulons tous les exécuter en utilisant une seule commande, nous avons différentes options :

  • Script SH pour Makefile

Nous devons définir une liste de dépendances de développement comme dans requirements-dev.txt.

isort~=5.10.1
black~=22.3.0
flake8~=4.0.1
flake8-print==4.0.0flake8-use-fstring==1.3flake8-tidy-imports==4.7.0pydocstyle[toml]=6.1.1mypy=0.960darglint~=1.8.1

Nous créons un fichier lint.sh (darglint est automatiquement lancé par flake8 lorsqu'il est installé dans le même environnement, donc pas besoin de le spécifier)

#!/bin/bash
set -o pipefail

flake8
black .
isort .
mypy .
pydocstyle

Et nous pouvons l'exécuter :

./lint.sh

Si vous souhaitez utiliser un Makefile :

lint:
  flake8
  black .
  isort .
  mypy .
  pydocstyle

Exécutez ensuite :

make lint
  • Pré-commit [Préféré]

Pré-valider est une autre alternative au regroupement de tous les outils et à leur exécution dans un environnement fermé dédié, de sorte que vous n'en ayez même pas besoin dans un environnement de développement.

pip install pre-commitputtingpre-commit run -a my-hook

De plus, il peut interagir avec le git hook concernant les fichiers modifiés lors de la validation, du push, etc. avant la soumission au stockage de code distant et éviter d'encombrer le CI pour des problèmes de lint et de style.

pre-commit install
# follow by git add/commit/push which trigger pre-commit

Configuration recommandée

.pre-commit-config.yaml

repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.2.0
    hooks:
      - id: check-ast
      - id: check-builtin-literals
      - id: check-docstring-first
        exclude: tests
      - id: check-merge-conflict
      - id: check-yaml
      - id: check-toml
      - id: debug-statements
      - id: end-of-file-fixer
      - id: trailing-whitespace
  - repo: https://github.com/pycqa/isort
    rev: 5.10.1
    hooks:
      - id: isort
  - repo: https://github.com/psf/black
    rev: 22.3.0
    hooks:
      - id: black
  - repo: https://github.com/pycqa/flake8
    rev: "4.0.1"
    hooks:
      - id: flake8
        additional_dependencies:
          - flake8-use-fstring
          - flake8-print
          - flake8-tidy-imports
  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: "v0.960"
    hooks:
      - id: mypy
        exclude: tests
        args: []
  - repo: https://github.com/pycqa/pydocstyle
    rev: 6.1.1
    hooks:
      - id: pydocstyle
        exclude: tests
        additional_dependencies: [toml]
  - repo: https://github.com/terrencepreilly/darglint
    rev: v1.8.1
    hooks:
    - id: darglint

Conclusion

Une fois que vous aurez convergé vers une configuration et choisi une méthode d'intégration, votre flux de travail sera stable et productif. De plus, ils n'évolueront probablement pas beaucoup à l'avenir, il est donc évident de maintenir une base de code avec de tels outils.

De plus, lorsqu'il est utilisé dès le début d'un projet, le coût est proche de zéro, mais lorsqu'il est appliqué à une base de code existante, la résolution de toutes les erreurs peut prendre un certain temps. Plus vous commencez tôt, mieux c'est !

Image sélectionnée par Kuznetcov_Konstantin

À propos

Qu'il s'agisse de simples photos, de fichiers PDF complexes ou de fichiers manuscrits, l'API de Mindee transforme les données de vos documents en JSON structuré de manière hautement fiable. Aucune formation sur les modèles n'est requise. Tous les alphabets et toutes les langues sont pris en charge.

,
,

Key Takeway

Key Takeway