Table des Contenus
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.
Exécutez pylint
sur votre code en utilisant ce
pylintrc.
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.
Il détecte les erreurs faciles à éviter, comme les fautes de frappe, l'utilisation de variables avant l'affectation, etc.
pylint
n'est pas parfait. Pour en tirer parti, nous devrons parfois écrire
autour de lui, supprimer ses avertissements ou le corriger.
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:
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 :
# ~$
pylint --list-msgs
Pour obtenir plus d'informations sur un message particulier, utilise :
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 :
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 :
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.
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.
Mécanisme de réutilisation de code d'un module dans une autre.
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
.
Les noms de modules peuvent toujours entrer en collision. Certains noms de modules sont trop longs pour être utilisés.
- Utilise
import x
pour importer des paquets et des modules. - Utilise
from x import y
oùx
est le préfixe du paquetage ety
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ésy
doivent être importés, siy
entre en conflit avec un nom de premier niveau défini dans le module actuel, ou siy
est un nom trop long pour être utilisé. - Utilise
import y as z
uniquement lorsquez
est une abréviation standard (par exemple,np
pournumpy
).
Par exemple, le module sound.effects.echo peut être importé comme suit :
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.
Exceptions à cette règle:
- Les symboles des modules suivants sont utilisés pour soutenir l'analyse statique et la vérification des types:
- Redirection vers le module `typing_extensions`
Importe chaque module en utilisant le chemin d'accès complet du module.
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.
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.
Tout nouveau code doit importer chaque module par son nom complet. Les importations doivent être comme suite :
# 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:
# 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/
où
jodie.py
se trouve également)
L'importation dans le code source suivant n'est pas correcte.
# 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
.
Des exceptions sont autorisées mais doivent être utilisées avec précaution.
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.
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.
Peut rendre le flux de contrôle confus. Il est facile de manquer des cas d'erreur lors des appels à la bibliothèque.
Les exceptions doivent respecter certaines conditions :
- 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 instructionsassert
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 :
# 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
# 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
-
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
). -
N'utilisez jamais d'instructions catch-all
except
: ou catchException
ouStandardError
, à 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.
-
Minimisez la quantité de code dans un bloc
try
/except
. Plus le corps detry
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 bloctry
/except
cache une véritable erreur. -
Utilisez la clause
finally
pour exécuter le code, qu'une exception soit été levée ou non dans le bloctry
. Cela est souvent utile pour le nettoyage, par exemple la fermeture d'un fichier.
Éviter les états globaux mutables.
Valeurs au niveau du module ou attributs de classe qui peuvent être modifiés au cours de l'exécution du programme.
Occasionnellement utile.
-
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.
É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.
Les fonctions ou classes locales imbriquées sont acceptables lorsqu'elles sont utilisées pour fermer une variable locale. Les classes internes sont acceptables.
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.
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.
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.
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.
C'est bon à utiliser pour des cas simples.
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
.
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.
Les compréhensions compliquées ou les expressions génératrices peuvent être difficiles à lire.
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.
# 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')
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)
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.
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").
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.
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.
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.
# 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(): ...
Utiliser des générateurs si nécessaire.
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.
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.
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.
-
Il est recommendé d'utilisez
"Yields :"
plutôt que"Returns :"
dans la docstring des fonctions de type générateur. -
Si le générateur gère une ressource coûteuse, assure toi de forcer le nettoyage.
-
Une bonne façon de faire le nettoyage est d'envelopper le générateur avec un gestionnaire de contexte PEP-0533.
Convient pour les expressions d'une ligne. Préférez les expressions
génératrices avec un lambda que les fonction map()
ou filter()
.
Les lambdas expressions définissent des fonctions anonymes dans une expression.
C'est très pratique.
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.
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
.
C'est bon pour des cas de programmation simple.
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
.
Plus court et plus pratique qu'une instruction if
complet.
La condition peut être difficile à localiser ou à percevoir si l'expression est longue.
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.
# 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'
)
# 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'
)