Skip to content

CodiTheck/google_python_style

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 

Repository files navigation

Guide de style Python de Google

Table des Contenus

1 Contexte

Python est le principal langage dynamique utilisé chez Google. Ce guide de style est une liste de choses à faire et à ne pas faire pour les programmes Python.
Pour vous aider à formater le code correctement, nous avons créé un fichier de configuration pour Vim. Pour Emacs, les paramètres par défaut devraient convenir.
De nombreuses équipes utilisent l'auto-formateur de Black ou Pyink pour éviter de se disputer sur le formatage.

2 Règle du langage Python

2.1 Lint

Exécutez pylint sur votre code en utilisant ce pylintrc.

2.1.1 Définition

pylint est un outil permettant de trouver des bugs et des problèmes de style dans le code source Python. Il trouve des problèmes qui sont typiquement détectés par un compilateur pour des langages moins dynamiques comme C et C++. En raison de la nature dynamique de Python, certains avertissements peuvent être incorrects; cependant, les avertissements erronés devraient être assez rares.

2.1.2 Avantage

Il détecte les erreurs faciles à éviter, comme les fautes de frappe, l'utilisation de variables avant l'affectation, etc.

2.1.3 Conséquence

pylint n'est pas parfait. Pour en tirer parti, nous devrons parfois écrire autour de lui, supprimer ses avertissements ou le corriger.

2.1.4 Décision

Assurez-vous d'exécuter pylint sur votre code. Supprimez les avertissements s'ils sont inappropriés afin que d'autres problèmes ne soient pas cachés. Pour supprimer les avertissements, vous pouvez définir un commentaire au niveau de la ligne:

</> PYTHON [01]
def do_PUT(self):  # WSGI name, so pylint: disable=invalid-name
  # ...

Les avertissements pylint sont chacun identifiés par un nom symbolique (empty-docstring). Les avertissements spécifiques à Google commencent par g-.

Si la raison de la suppression n'est pas claire à partir du nom symbolique, ajoute une explication. La suppression de cette manière a l'avantage que nous pouvons facilement rechercher les suppressions et les revoir. Tu peux obtenir une liste des avertissements de pylint en faisant :

>_ cmd@01:~$
# ~$
pylint --list-msgs

Pour obtenir plus d'informations sur un message particulier, utilise :

>_ cmd@02:~$
pylint --help-msg=invalid-name

Il faut plutôt utiliser pylint: disable au lieu d'utiliser l'ancienne forme dépréciée pylint: disable-msg.

Les avertissements concernant les arguments inutilisés peuvent être supprimés en supprimant les variables au début de la fonction. Il faut toujours inclure un commentaire expliquant pourquoi tu les supprime. "Unused." est suffisant. Par exemple :

</> PYTHON [02]
def viking_cafe_order(spam: str, beans: str, eggs: str | None = None) -> str:
    del beans, eggs  # Unused by vikings.
    return spam + spam + spam

Au fait, s'il y a des arguments d'une fonction que tu n'utilise pas, pylint te mettra des avertissements.

D'autres formes courantes de suppression de cet avertissement consistent à utiliser '' comme identifiant pour l'argument inutilisé ou à préfixer le nom de l'argument avec 'unused', ou à les assigner à '_'. Par exemple :

</> PYTHON [03]
def viking_cafe_order(
  spam: str,
  _beans: str,
  unused_eggs: str | None = None
) -> str:
  # ...

Ces formes sont autorisées mais ne sont plus encouragées. Elles brisent les appelants qui passent les arguments par leur nom et n'imposent pas que les arguments soient réellement inutilisés. Elles empêchent l'utilisateur de la fonction de passer les arguments par leur nom, comme viking_cafe_order(spam="myspam", beans="ok", eggs=None) et ne spécifie pas à l'utilisateur de la fonction que ces paramètres ne sont plus utilisés.

2.2 Importations

Utilisez l'instruction import uniquement pour importer les paquets et les modules, et non pour importer les classes ou les fonctions de façon individuelles.

2.2.1 Définition

Mécanisme de réutilisation de code d'un module dans une autre.

2.2.2 Avantage

La convention de gestion des espaces de noms est simple. La source de chaque identifiant est indiquée de manière cohérente; x.Obj indique que l'objet Obj est défini dans le module x.

2.2.3 Conséquences

Les noms de modules peuvent toujours entrer en collision. Certains noms de modules sont trop longs pour être utilisés.

2.2.4 Décision

  • Utilise import x pour importer des paquets et des modules.
  • Utilise from x import yx est le préfixe du paquetage et y est le nom du module sans préfixe. Tu sais déjà programmer en python donc pas besoin de t'expliquer.
  • Utilise from x import y as z si deux modules nommés y doivent être importés, si y entre en conflit avec un nom de premier niveau défini dans le module actuel, ou si y est un nom trop long pour être utilisé.
  • Utilise import y as z uniquement lorsque z est une abréviation standard (par exemple, np pour numpy).

Par exemple, le module sound.effects.echo peut être importé comme suit :

</> PYTHON [04]
from sound.effects import echo

# ...

echo.EchoFilter(input, output, delay=0.7, atten=4)

N'utilise pas de noms relatifs dans les importations. Même si le module se trouve dans le même paquet, utilise le nom complet du paquet. Cela permet d'éviter d'importer involontairement un paquet deux fois.

2.2.4.1 Exceptions

Exceptions à cette règle:

2.3 Les paquets

Importe chaque module en utilisant le chemin d'accès complet du module.

2.3.1 Avantages

Permet d'éviter les conflits dans les noms de modules ou les importations incorrectes dues au fait que le chemin de recherche des modules ne correspond pas à ce que l'auteur attendait. Facilite la recherche de modules.

2.3.2 Conséquence

Rend le déployement du code plus difficile parce qu'il faut reproduire la hiérarchie des paquets. Mais grâce aux mécanismes modernes de déploiement, ce n'est plus un problème.

2.3.3 Décision

Tout nouveau code doit importer chaque module par son nom complet. Les importations doivent être comme suite :

</> PYTHON [05]
# CODE CORRECT:
# Faire référence à absl.flags dans le code avec le nom complet (verbose).
import absl.flags
from doctor.who import jodie

_FOO = absl.flags.DEFINE_string(...)

Ou, encore le code suivant:

</> PYTHON [06]
# CODE CORRECT:
# Référencer `flags` dans le code avec seulement le nom du module (commun).
from absl import flags
from doctor.who import jodie

_FOO = flags.DEFINE_string(...)

(en supposant que ce fichier de code suivant se trouve dans doctor/who/jodie.py se trouve également) L'importation dans le code source suivant n'est pas correcte.

</> PYTHON [07]
# CODE PAS CORRECT:
# On ne sait pas exactement quel module l'auteur veut et ce qui sera importé.
# Le comportement réel de l'importation dépend de facteurs externes contrôlant
# sys.path. Quel module possible de jodie l'auteur voulait-il importer ?
import jodie

Le répertoire dans lequel se trouve le binaire principal ne doit pas être considéré comme se trouvant dans sys.path, même si c'est le cas dans certains environnements. Pour cela, le programmeur doit supposer que import jodie fait référence à un module tiers ou de premier niveau nommé jodie, et non à un fichier local jodie.py.

2.4 Les exceptions

Des exceptions sont autorisées mais doivent être utilisées avec précaution.

2.4.1 Définition

Les exceptions permettent de stopper le flux de contrôle normal, c'est à dire de l'exécution normale, pour gérer des erreurs ou d'autres conditions exceptionnelles.

2.4.2 Avantage

Le flux de contrôle du code de fonctionnement normal n'est pas encombré par le code de traitement des erreurs. Il permet également au flux de contrôle de sauter plusieurs cadres lorsqu'une certaine condition se produit, par exemple en revenant de N fonctions imbriquées en une seule étape au lieu de devoir passer par les codes probablement truffé d'erreur.

2.4.3 Conséquence

Peut rendre le flux de contrôle confus. Il est facile de manquer des cas d'erreur lors des appels à la bibliothèque.

2.4.4 Décision

Les exceptions doivent respecter certaines conditions :

  1. Utilisez les classes d'exception intégrées lorsque cela s'avère utile. Par exemple, levez une ValueError pour signaler une erreur de programmation, comme une condition préalable non respectée (par exemple, si l'on vous a transmis un nombre négatif alors que vous aviez besoin d'un nombre positif). N'utilisez pas les instructions assert pour valider les valeurs des arguments d'une API publique. assert est utilisé pour garantir la correction interne, et non pour imposer une utilisation correcte ou pour indiquer qu'un événement inattendu s'est produit. Si une exception est souhaitée dans ces derniers cas, utilisez une instruction raise. Par exemple :
</> PYTHON [08]
# CODE CORRECTE:
def connect_to_next_port(self, minimum: int) -> int:
  """Connects to the next available port.

  Args:
    minimum: A port value greater or equal to 1024.

  Returns:
    The new minimum port.

  Raises:
    ConnectionError: If no available port is found.
  """
  if minimum < 1024:
    # Notez que cette levée de ValueError n'est pas mentionnée dans la 
    # section "Raises :" de la doc
    # dans la section "Raises :" de la doc car il n'est pas approprié de
    # garantir cette réaction comportementale spécifique à une mauvaise
    # utilisation de l'API.
    raise ValueError(f'Min. port must be at least 1024, not {minimum}.')

  port = self._find_next_open_port(minimum)
  if port is None:
    raise ConnectionError(
        f'Could not connect to service on port {minimum} or higher.')

  assert port >= minimum, (
      f'Unexpected port {port} when minimum was {minimum}.')
  return port
</> PYTHON [09]
# CODE PAS CORRECTE:
def connect_to_next_port(self, minimum: int) -> int:
  """Connects to the next available port.

  Args:
    minimum: A port value greater or equal to 1024.

  Returns:
    The new minimum port.
  """
  assert minimum >= 1024, 'Minimum port must be at least 1024.'
  port = self._find_next_open_port(minimum)
  assert port is not None
  return port
  1. Les bibliothèques ou les paquets peuvent définir leurs propres exceptions. Dans ce cas, ils doivent hériter d'une classe d'exception existante. Les noms des exceptions doivent se terminer par Error et ne doivent pas introduire de répétition (foo.FooError).

  2. N'utilisez jamais d'instructions catch-all except : ou catch Exception ou StandardError, à moins d'être :

  • en soulevant à nouveau l'exception, ou
  • la création d'un point d'isolation dans le programme où les exceptions ne sont pas propagées mais enregistrées et supprimées, comme la protection d'un thread contre le plantage en gardant son bloc le plus externe.

Python est très tolérant à cet égard et except : attrape tout, y compris les noms mal orthographiés, les appels à sys.exit(), les interruptions Ctrl+C, les échecs d'unittest et toutes sortes d'autres exceptions que vous ne voulez tout simplement pas attraper.

  1. Minimisez la quantité de code dans un bloc try/except. Plus le corps de try est important, plus il est probable qu'une exception soit soulevée par une ligne de code à laquelle vous ne vous attendiez pas. Dans ce cas, le bloc try/except cache une véritable erreur.

  2. Utilisez la clause finally pour exécuter le code, qu'une exception soit été levée ou non dans le bloc try. Cela est souvent utile pour le nettoyage, par exemple la fermeture d'un fichier.

2.5 État global mutable

Éviter les états globaux mutables.

2.5.1 Définition

Valeurs au niveau du module ou attributs de classe qui peuvent être modifiés au cours de l'exécution du programme.

2.5.2 Avantage

Occasionnellement utile.

2.5.3 Conséquence

  • Casse l'encapsulation : Une telle conception peut rendre difficile la réalisation d'objectifs valables. Par exemple, si l'état global est utilisé pour gérer la connexion à une base de données, il devient difficile de se connecter à deux bases de données différentes en même temps (par exemple pour calculer les différences lors d'une migration). Des problèmes similaires se posent facilement avec les registres globaux.

  • Peut modifier le comportement du module pendant l'importation, car les affectations aux variables globales sont effectuées lorsque le module est importé pour la première fois.

2.5.4 Décision

Éviter les états globaux mutables.

Dans les rares cas où l'utilisation d'un état global est justifiée, les entités globales mutables doivent être déclarées au niveau du module ou en tant qu'attribut de classe et rendues internes en ajoutant un _ au nom. Si nécessaire, l'accès externe à l'état global mutable doit se faire par le biais de fonctions publiques ou de méthodes de classe. Voir la section "Nommage" ci-dessous. Veuillez expliquer les raisons pour lesquelles l'état global mutable est utilisé dans un commentaire ou dans un document lié à un commentaire.

Les constantes au niveau du module sont autorisées et encouragées. Par exemple : _MAX_HOLY_HANDGRENADE_COUNT = 3 pour une constante d'utilisation interne ou SIR_LANCELOTS_FAVORITE_COLOR = "blue" pour une constante de l'API publique. Les constantes doivent être nommées en utilisant des majuscules et des traits de soulignement. Voir la section "Nommage" ci-dessous.

2.6 Classes et fonctions imbriquées/locales/intérieures

Les fonctions ou classes locales imbriquées sont acceptables lorsqu'elles sont utilisées pour fermer une variable locale. Les classes internes sont acceptables.

2.6.1 Définition

Une classe peut être définie à l'intérieur d'une méthode, d'une fonction ou d'une classe. Une fonction peut être définie à l'intérieur d'une méthode ou d'une fonction. Les fonctions imbriquées ont un accès en lecture seule aux variables définies dans les champs d'application qui les entourent.

2.6.2 Avantages

Permet de définir des classes et des fonctions utilitaires qui ne sont utilisées que dans une portée très limitée. Couramment utilisé pour la mise en œuvre de décorateurs.

2.6.3 Conséquence

Les fonctions et classes imbriquées ne peuvent pas être testées directement. L'imbrication peut rendre la fonction extérieure plus longue et moins lisible.

2.6.4 Décision

Les fonctions imbriquées sont très bien avec quelques mises en garde. Éviter les fonctions ou classes imbriquées, sauf pour fermer une valeur locale autre que self ou cls. Ne pas imbriquer une fonction simplement pour la cacher aux utilisateurs d'un module. Au lieu de cela, préfixez son nom par un _ au niveau du module afin qu'il soit toujours accessible par les tests.

2.7 Comprehensions & Expressions de Generator

C'est bon à utiliser pour des cas simples.

2.7.1 Définition

Les compréhensions list, dict et set ainsi que les expressions génératrices constituent un moyen concis et efficace de créer des types de conteneurs et des itérateurs sans avoir recours aux boucles traditionnelles, aux map(), aux filter() ou aux lambda.

2.7.2 Avantages

Les compréhensions simples peuvent être plus claires et plus simples que d'autres techniques de création de dict, de list ou set. Les expressions génératrices peuvent être très efficaces, puisqu'elles évitent la création d'une liste entièrement.

2.7.3 Conséquence

Les compréhensions compliquées ou les expressions génératrices peuvent être difficiles à lire.

2.7.4 Décision

Bon pour être utilisé pour des cas simples. Chaque portion doit tenir sur une ligne : mapping expression, clause for, expression filter. Les multiples clause for ou expression filter ne sont pas permits. Utilisez plutôt des boucles lorsque les choses deviennent plus compliquées.

</> PYTHON [10]
# CODE CORRECT:
result = [mapping_expr for value in iterable if filter_expr]

result = [{'key': value} for value in iterable
          if a_long_filter_expression(value)]

result = [complicated_transform(x)
          for x in iterable if predicate(x)]

descriptive_name = [
    transform({'key': key, 'value': value}, color='black')
    for key, value in generate_iterable(some_input)
    if complicated_condition_is_met(key, value)
]

result = []
for x in range(10):
    for y in range(5):
        if x * y > 10:
            result.append((x, y))

return {x: complicated_transform(x)
        for x in long_generator_function(parameter)
        if x is not None}

squares_generator = (x**2 for x in range(10))

unique_names = {user.name for user in users if user is not None}

eat(jelly_bean for jelly_bean in jelly_beans
    if jelly_bean.color == 'black')
</> PYTHON [11]
CODE PAS CORRECT:
  result = [complicated_transform(
                x, some_argument=x+1)
            for x in iterable if predicate(x)]

  result = [(x, y) for x in range(10) for y in range(5) if x * y > 10]

  return ((x, y, z)
          for x in range(5)
          for y in range(5)
          if x != y
          for z in range(5)
          if y != z)

2.8 Itérateurs et opérateurs par défaut

Utilisez des itérateurs et des opérateurs par défaut pour les types qui les prennent en charge, comme les listes, les dictionnaires et les fichiers.

2.8.1 Définition

Les types de conteneurs, comme les dictionnaires et les listes, définissent des itérateurs par défaut et des opérateurs de test d'appartenance ("in" et "not in").

2.8.2 Avantages

Les itérateurs et opérateurs par défaut sont simples et efficaces. Ils expriment l'opération directement, sans appel de méthode supplémentaire. Une fonction qui utilise des opérateurs par défaut est générique. Elle peut être utilisée avec n'importe quel type prenant en charge l'opération.

2.8.3 Conséquences

Il est impossible de connaître le type d'un objet en lisant le nom des méthodes (à moins que la variable ne comporte des annotations de type). Mais cela peut être également un avantage.

2.8.4 Solutions

Utilise les itérateurs et les opérateurs par défaut pour les types qui les prennent en charge, comme les listes, les dictionnaires et les fichiers. Les types intégrés définissent également des méthodes d'itérateur. Il faut préférer ces méthodes à celles qui renvoient des listes, sauf que tu ne dois pas modifier un conteneur pendant que tu le parcour.

</> PYTHON [12]
# CODE CORRECTE:
for key in adict: ...
if obj in alist: ...
for line in afile: ...
for k, v in adict.items(): ...
# CODE PAS CORRECTE:
for key in adict.keys(): ...
for line in afile.readlines(): ...

2.9 Générateurs

Utiliser des générateurs si nécessaire.

2.9.1 Définition

Une fonction generator renvoie un itérateur qui produit une valeur chaque fois qu'il exécute une instruction yield. Après avoir produit une valeur, l'état d'exécution de la fonction generator est suspendu jusqu'à ce que la valeur suivante soit demandée.

2.9.2 Avantages

Code plus simple, car l'état des variables locales et le flux de contrôle sont préservés à chaque appel. Un générateur utilise moins de mémoire qu'une fonction qui crée une liste entière de valeurs en une seule fois.

2.9.3 Conséquences

Les variables locales du générateur ne seront pas libérées jusqu'à ce que le générateur soit consommé jusqu'à épuisement ou qu'il soit lui-même libéré de la mémoire.

2.9.4 Solutions

  1. Il est recommendé d'utilisez "Yields :" plutôt que "Returns :" dans la docstring des fonctions de type générateur.

  2. Si le générateur gère une ressource coûteuse, assure toi de forcer le nettoyage.

  3. Une bonne façon de faire le nettoyage est d'envelopper le générateur avec un gestionnaire de contexte PEP-0533.

2.10 Fonction Lambda

Convient pour les expressions d'une ligne. Préférez les expressions génératrices avec un lambda que les fonction map() ou filter().

2.10.1 Définition

Les lambdas expressions définissent des fonctions anonymes dans une expression.

2.10.2 Avantages

C'est très pratique.

2.10.3 Conséquences

Elles sont plus difficiles à lire et à déboguer que les fonctions locales. L'expressivité est limitée car la fonction ne peut contenir qu'une seule expression ou instruction.

2.10.4 Décision

Il est possible de les utiliser pour des définitions d'une ligne d'instruction. Si le code à l'intérieur de la fonction lambda est plus long que 60-80 caractères, il est préférable de la définir comme une fonction normale.

Pour les opérations courantes comme la multiplication, utilisez les fonctions du module operator au lieu des fonctions lambda. Par exemple, il faut préférer operator.mul à lambda x, y : x * y.

2.11 Expressions conditionnelles

C'est bon pour des cas de programmation simple.

2.11.1 Définition

Les expressions conditionnelles (parfois appelées "opérateurs ternaires") sont des mécanismes qui fournissent une syntaxe plus courte pour les instructions if. Par exemple : x = 1 if condition else 2.

2.11.2 Avantages

Plus court et plus pratique qu'une instruction if complet.

2.11.3 Conséquences

La condition peut être difficile à localiser ou à percevoir si l'expression est longue.

2.11.4

Utilisable pour les cas simples. Chaque partie doit tenir sur une ligne : expression_planA,if condition else expression_plan_B. Il faut utiliser une instruction if complète lorsque les choses deviennent plus complexe.

</> PYTHON [13]
# CODE CORRECTE:
one_line = 'yes' if predicate(value) else 'no'
slightly_split = ('yes' if predicate(value) else 'no, nein, nyet')
the_longest_ternary_style_that_can_be_done = (
        'yes, true, affirmative, confirmed, correct' if predicate(value)
        else 'no, false, negative, nay'
)
</> PYTHON [14]
# CODE PAS CORRECT:
bad_line_breaking = ('yes' if predicate(value) else 'no')
portion_too_long = (
  'yes' if some_long_module.some_long_predicate_function(
    really_long_variable_name
  )
  else 'no, false, negative, nay'
)