bonnes et de mauvaises pratiques de codage en Python

Bonnes et mauvaises pratiques de codage en Python

Partagez sur:

Cet article présente plusieurs exemples de bonnes et de mauvaises pratiques de codage en Python que vous êtes susceptible de rencontrer souvent.

LIRE AUSSI: Comment installer Python 3.9 sur Ubuntu 20.04

Python est un langage de programmation multi-paradigme de haut niveau qui met l’accent sur la lisibilité. Il est développé, maintenu et souvent utilisé en suivant les règles appelées The Zen of Python ou PEP 20.

Utilisation du déballage pour écrire du code concis

L’emballage et le déballage sont de puissantes fonctionnalités Python. Vous pouvez utiliser le déballage pour attribuer des valeurs à vos variables:

>>> a, b = 2, 'my-string'
>>> a
2
>>> b
'my-string'

Vous pouvez exploiter ce comportement pour implémenter probablement le swap de variables le plus concis et le plus élégant du monde entier de la programmation informatique:

>>> a, b = b, a
>>> a
'my-string'
>>> b
2

C’est génial! Le déballage peut être utilisé pour l’affectation à plusieurs variables dans des cas plus complexes. Par exemple, vous pouvez attribuer comme ceci:

>>> x = (1, 2, 4, 8, 16)
>>> a = x[0]
>>> b = x[1]
>>> c = x[2]
>>> d = x[3]
>>> e = x[4]
>>> a, b, c, d, e
(1, 2, 4, 8, 16)

Mais à la place, vous pouvez utiliser une approche plus concise et sans doute plus lisible:

>>> a, b, c, d, e = x
>>> a, b, c, d, e
(1, 2, 4, 8, 16)

C’est cool, non? Mais ça peut être encore plus cool:

>>> a, *y, e = x
>>> a, e, y
(1, 16, [2, 4, 8])

Le fait est que la variable avec * collecte les valeurs non affectées à d’autres.

Utilisation du chaînage pour écrire du code concis

Python vous permet d’enchaîner les opérations de comparaison. Ainsi, vous n’avez pas besoin d’utiliser et de vérifier si deux ou plusieurs comparaisons sont vraies:

>>> x = 4
>>> x >= 2 and x <= 8
True

Au lieu de cela, vous pouvez écrire ceci sous une forme plus compacte, comme le font les mathématiciens:

>>> 2 <= x <= 8
True
>>> 2 <= x <= 3
False

Python prend également en charge les affectations chaînées. Donc, si vous souhaitez attribuer la même valeur à plusieurs variables, vous pouvez le faire de manière simple:

>>> x = 2
>>> y = 2
>>> z = 2

Une manière plus élégante consiste à utiliser le déballage:

>>> x, y, z = 2, 2, 2

Cependant, les choses s’améliorent encore avec les affectations en chaîne:

>>> x = y = z = 2
>>> x, y, z
(2, 2, 2)

 

Soyez prudent lorsque votre valeur est modifiable! Toutes les variables font référence à la même instance.

Vérification contre None

None est un objet spécial et unique en Python. Il a un objectif similaire, comme null dans les langages de type C.

Il est possible de vérifier si une variable y fait référence avec les opérateurs de comparaison == et! =:

>>> x, y = 2, None
>>> x == None
False
>>> y == None
True
>>> x != None
True
>>> y != None
False

Cependant, une manière plus pythonique et souhaitable utilise est et n’est pas:

>>> x is None
False
>>> y is None
True
>>> x is not None
True
>>> y is not None
False

De plus, vous devriez préférer utiliser la construction is not x is not None plutôt que son alternative moins lisible non (x est None).

Itération sur des séquences et des mappages

Vous pouvez implémenter des itérations et des boucles for en Python de plusieurs manières. Python propose des classes intégrées pour le faciliter.

Dans presque tous les cas, vous pouvez utiliser la plage pour obtenir un itérateur qui donne des entiers:

>>> x = [1, 2, 4, 8, 16]
>>> for i in range(len(x)):
...     print(x[i])
... 
1
2
4
8
16

 

Cependant, il existe un meilleur moyen d’itérer sur une séquence:

>>> for item in x:
...     print(item)
... 
1
2
4
8
16

 

Mais que se passe-t-il si vous souhaitez effectuer une itération dans l’ordre inverse? Bien sûr, la gamme est à nouveau une option:

>>> for i in range(len(x)-1, -1, -1):
...     print(x[i])
... 
16
8
4
2
1

Inverser la séquence est une manière plus élégante:

>>> for item in x[::-1]:
...     print(item)
... 
16
8
4
2
1

La méthode pythonique consiste à utiliser inversé pour obtenir un itérateur qui donne les éléments d’une séquence dans l’ordre inverse:

>>> for item in reversed(x):
...     print(item)
... 
16
8
4
2
1

Parfois, vous avez besoin à la fois des éléments d’une séquence et des indices correspondants:

>>> for i in range(len(x)):
...     print(i, x[i])
... 
0 1
1 2
2 4
3 8
4 16

 

Il est préférable d’utiliser enumerate pour obtenir un autre itérateur qui produit les tuples avec les indices et les éléments:

>>> for i, item in enumerate(x):
...     print(i, item)
... 
0 1
1 2
2 4
3 8
4 16

 

C’est super. Mais que se passe-t-il si vous souhaitez itérer sur deux séquences ou plus? Bien sûr, vous pouvez à nouveau utiliser la gamme:

>>> y = 'abcde'
>>> for i in range(len(x)):
...     print(x[i], y[i])
... 
1 a
2 b
4 c
8 d
16 e

Dans ce cas, Python propose également une meilleure solution. Vous pouvez appliquer un zip et obtenir des tuples des éléments correspondants:

>>> for item in zip(x, y):
...     print(item)
... 
(1, 'a')
(2, 'b')
(4, 'c')
(8, 'd')
(16, 'e')

Vous pouvez le combiner avec le déballage:

>>> for x_item, y_item in zip(x, y):
...     print(x_item, y_item)
... 
1 a
2 b
4 c
8 d
16 e

Veuillez garder à l’esprit que range peut être très utile. Cependant, il existe des cas (comme ceux indiqués ci-dessus) où il existe des alternatives plus pratiques. Itérer sur un dictionnaire donne ses clés:

>>> z = {'a': 0, 'b': 1}
>>> for k in z:
... print(k, z[k])
... 
a 0
b 1

 

Cependant, vous pouvez appliquer la méthode .items() et obtenir les tuples avec les clés et les valeurs correspondantes:

>>> for k, v in z.items():
...     print(k, v)
... 
a 0
b 1

Vous pouvez également utiliser les méthodes .keys() et .values() pour parcourir respectivement les clés et les valeurs.

Comparaison avec zéro

Lorsque vous avez des données numériques et que vous devez vérifier si les nombres sont égaux à zéro, vous pouvez mais pas obligé d’utiliser les opérateurs de comparaison == et ! =:

>>> x = (1, 2, 0, 3, 0, 4)
>>> for item in x:
...     if item != 0:
...         print(item)
... 
1
2
3
4

La méthode pythonique consiste à exploiter le fait que zéro est interprété comme faux dans un contexte booléen, tandis que tous les autres nombres sont considérés comme vrai:

>>> bool(0)
False
>>> bool(-1), bool(1), bool(20), bool(28.4)
(True, True, True, True)

En gardant cela à l’esprit, vous pouvez simplement utiliser if item au lieu de if item! = 0:

>>> for item in x:
...     if item:
...         print(item)
... 
1
2
3
4

Vous pouvez suivre la même logique et utiliser if not item au lieu de if item == 0.

Éviter les arguments facultatifs mutables

Python a un système très flexible de fourniture d’arguments aux fonctions et méthodes. Les arguments facultatifs font partie de cette offre.

Mais soyez prudent: vous ne souhaitez généralement pas utiliser d’arguments optionnels mutables. Prenons l’exemple suivant:

>>> def f(value, seq=[]):
...     seq.append(value)
...     return seq

À première vue, cela ressemble à ça, si vous ne fournissez pas de seq, f() ajoute une valeur à une liste vide et renvoie quelque chose comme [valeur]:

>>> f(value=2)
[2]

Ça a l’air bien, non? Non! Considérez les exemples suivants:

>>> f(value=4)
[2, 4]
>>> f(value=8)
[2, 4, 8]
>>> f(value=16)
[2, 4, 8, 16]

Surpris? Confus? Si c’est le cas, vous n’êtes pas le seul. Il semble que la même instance d’un argument optionnel (liste dans ce cas) soit fournie chaque fois que la fonction est appelée. Peut-être que parfois vous voudrez exactement ce que fait le code ci-dessus. Cependant, il est beaucoup plus probable que vous deviez éviter cela. Vous pouvez éviter cela avec une logique supplémentaire. L’un des moyens est le suivant:

>>> def f(value, seq=None):
...     if seq is None:
...         seq = []
...     seq.append(value)
...     return seq

Une version plus courte est:

>>> def f(value, seq=None):
...     if not seq:
...         seq = []
...     seq.append(value)
...     return seq

Maintenant, vous obtenez un comportement différent:

>>> f(value=2)
[2]
>>> f(value=4)
[4]
>>> f(value=8)
[8]
>>> f(value=16)
[16]

Dans la plupart des cas, c’est ce que l’on veut.

 

Éviter les Getters et Setters classiques

Python permet de définir les méthodes getter et setter de la même manière que C ++ et Java:

>>> class C:
...     def get_x(self):
...         return self.__x
...     def set_x(self, value):
...

Voici comment vous pouvez les utiliser pour obtenir et définir l’état d’un objet:

>>> c = C()
>>> c.set_x(2)
>>> c.get_x()
2

Dans certains cas, c’est la meilleure façon de faire le travail. Cependant, il est souvent plus élégant de définir et d’utiliser des propriétés, en particulier dans les cas simples:

>>> class C:
...     @property
...     def x(self):
...         return self.__x
...     @x.setter
...     def x(self, value):
...         self.__x = value

Les propriétés sont considérées comme plus pythoniques que les getters et setters classiques. Vous pouvez les utiliser de la même manière qu’en C#, c’est-à-dire de la même manière que les attributs de données ordinaires:

>>> c = C()
>>> c.x = 2
>>> c.x
2

 

Éviter d’accéder aux membres de la classe protégés

Python n’a pas de vrais membres de classe privés. Cependant, il existe une convention qui stipule que vous ne devez pas accéder ni modifier les membres commençant par le trait de soulignement (_) en dehors de leurs instances. Ils ne sont pas garantis pour préserver le comportement existant.

Par exemple, considérez le code:

>>> class C:
...     def __init__(self, *args):
...         self.x, self._y, self.__z = args
... 
>>> c = C(1, 2, 4)

Les instances de la classe C ont trois membres de données: .x, ._y et ._Cz. Si le nom d’un membre commence par un double trait de soulignement (dunder), il devient mutilé, ce qui est modifié. C’est pourquoi vous avez ._Cz au lieu de .__ z. Maintenant, vous pouvez accéder ou modifier directement .x:

>>> c.x  # OK
1

Vous pouvez également accéder ou modifier ._y depuis l’extérieur de son instance, mais cela est considéré comme une mauvaise pratique:

>>> c._y  # Possible, but a bad practice!
2

Vous ne pouvez pas accéder au .z car il est mutilé, mais vous pouvez accéder ou modifier le ._Cz:

>>> c.__z # Error!
Traceback (most recent call last):
File "", line 1, in 
AttributeError: 'C' object has no attribute '__z'
>>> c._C__z # Possible, but even worse!
4
>>>

Vous devriez éviter de faire cela. L’auteur de la classe commence probablement les noms par le (s) soulignement (s) pour vous dire, « ne l’utilisez pas ».

Utilisation des gestionnaires de contexte pour libérer des ressources

Parfois, il est nécessaire d’écrire le code pour gérer correctement les ressources. C’est souvent le cas lorsque vous travaillez avec des fichiers, des connexions à une base de données ou d’autres entités avec des ressources non gérées. Par exemple, vous pouvez ouvrir un fichier et le traiter:

>>> my_file = open('filename.csv', 'w')
>>> # do something with `my_file`

Pour gérer correctement la mémoire, vous devez fermer ce fichier une fois le travail terminé:

>>> my_file = open('filename.csv', 'w')
>>> # do something with `my_file and`
>>> my_file.close()

Le faire de cette façon est mieux que de ne pas le faire du tout. Mais que se passe-t-il si une exception se produit lors du traitement de votre fichier? Ensuite, my_file.close () n’est jamais exécuté. Vous pouvez gérer cela avec une syntaxe de gestion des exceptions ou avec des gestionnaires de contexte. La deuxième façon signifie que vous mettez votre code dans le avec un bloc:

>>> with open('filename.csv', 'w') as my_file:
...     # do something with `my_file`

L’utilisation du bloc with signifie que les méthodes spéciales .enter() et .exit() sont appelées, même en cas d’exceptions.

Ces méthodes devraient prendre soin des ressources. Vous pouvez obtenir des constructions particulièrement robustes en combinant les gestionnaires de contexte et la gestion des exceptions.

Conseils stylistiques

Le code Python doit être élégant, concis et lisible. Ça devrait être beau.

 

La ressource ultime sur la façon d’écrire du beau code Python est Style Guide for Python Code ou PEP 8. Vous devez absolument le lire si vous voulez coder en Python.

Conclusions

Cet article donne plusieurs conseils sur la façon d’écrire un code plus efficace, plus lisible et plus concis. En bref, il montre comment écrire un code Pythonic. De plus, PEP 8 fournit le guide de style pour le code Python, et PEP 20 représente les principes du langage Python.

 

Profitez de l’écriture de code pythonique, utile et beau!

 

Merci pour la lecture.


Partagez sur: