Bien programmer avec Python : bonnes pratiques, pièges et astuces …

18 Mai 2020
bien-programmer-en-python
Bien programmer en Python, c’est tout un art … L’auteur de ces lignes revient sur les pièges à éviter, et vous indique quelques bonnes astuces à mettre en pratique.

Les dialectes du langage Python

Voici les principaux dialectes de Python. La seule expérience de l’auteur est PyPy, utilisé sommairement pour tenter d’exécuter quelques programmes plus rapidement.

Le GIL (« Global Interpreter Lock ») est une limitation de Python qui empêche d’exécuter efficacement plusieurs petits process (« threads ») en parallèle.

dialectes_du_langage_python

L’implémentation Micro Python réalise quelques coupes drastiques dans le langage à des fins d’optimisation. Il serait intéressant d’explorer le portage de programmes dans ce dialecte pour espérer un gain de performances (pour des applications classiques) – une piste à explorer...

 

Entrons un peu dans le détail du langage

 

La vertu de l’indentation

La principale originalité de Python par rapport aux autres langages réside dans le choix de la valeur sémantique de l’indentation.

Illustrons par un exemple sur le code de la fonction de Fibonacci :

identation_langage_python

On constate que Python n’a pas besoin du terminateur « ; » qui doit finir chaque instruction du langage C. Voilà un premier gain en lisibilité, qui est loin d’être le seul.

On constate également que C utilise des accolades {et} pour délimiter les blocs, et que le compilateur C accepte une indentation fantaisiste (qui nuit à la lisibilité du code).

identation_langage_python

Ainsi, les deux programmes Python suivants ne fourniront pas le même résultat :

 

identation_langage_python

La bonne pratique en Python3 est d’indenter avec des blocs de 4 espaces. Par définition, un programme Python ne peut pas être mal indenté, puisque l’indentation est sémantique et non décorative. 

Cette culture de l’indentation est une des principales causes d’aversion pour le langage de la part de ses détracteurs (surtout s’ils utilisent un éditeur peu approprié qui mélange espaces et tabulations de manière incontrôlée).

 

Mutabilité et non mutabilité, telle est la question

En Python, un type « mutable » (muable en français) est le type d’un objet qui peut être changé, et « immutable » (immuable en français) le contraire.

Ci-dessous les principaux types natifs du langage (on reste sur Python 3, certaines descriptions sont différentes en Python 2).

mutabilite_et_non_mutabilite_langage_python

Première remarque : il n’y a pas de limite sur la valeur des entiers. Le développeur n’a pas à se préoccuper de choisir des short, des int, des long, qui d’ailleurs n’ont pas les mêmes contraintes selon la machine utilisée.

Illustrons l’avantage sur cet exemple (l’auteur a volontairement entré des valeurs entières qui dépassent les capacités de stockage dans les langages Java et C) :

mutabilite_et_non_mutabilite_langage_python

 

Deuxième remarque : il n’y a aucun impératif d’homogénéité dans les types d’une séquence (ce qui est plutôt une différence par rapport aux autres langages)

Troisième remarque : le type str n’est pas un tableau de caractère. Il est possible de passer d’un type str à un type bytes par le biais des fonctions encode et decode :

mutabilite_et_non_mutabilite_langage_python_tableau_3

Cette particularité distingue Python3 de Python2.

Quatrième remarque : le type dict, très utilisé en Python, permet notamment d’accéder rapidement à des grosses tables de données car l’implémentation garantit un calcul rapide de la valeur à partir de la clef. De la même manière le test de présence d’un élément dans un ensemble (un set) est rapide.

 

Typage dynamique : si ça « coinque », c’est un canard

En Python, on ne type pas les variables, l’interpréteur trouve le type « comme un grand » à partir de la valeur.

l = ['a', 'b', 'c']

type(l)  → <class 'list'>

 

En Python, une variable peut changer de type :

 

l = ['a', 'b', 'c']

l = ('a', 'b', 'c') # Now a  Tuple

 

Inconvénient : un typage statique connu à la compilation permet de nombreuses optimisations, ce qui est donc impossible en Python. De plus un changement de type est souvent le signe d’une étourderie du programmeur non détectée en Python mais qui le serait dans un langage statiquement typé. Les contraintes de type des langages à typage statique (défini avant compilation) permettent de détecter des erreurs de codage. Nous reviendrons à la fin de l’article sur cet aspect et comment cet écueil peut être dissipé.

Dans un langage statiquement typé ces deux constructions n’auraient pas été possibles.

 

L’équivalent de la première serait, en langage C :

 

char *l[3] = {«a», «b», «c»} // type of ‘l’ is array of pointer to char

 

Affectation et portée

En Python, une variable est une « référence d’objet ».

Le code suivant :

b = Dog()

a = b

signifie donc que a et b référencent le même objet de la classe Dog.

En C++ on dirait un « alias ».

 

Par ailleurs il est possible d’écrire :

a = b = c # affectation multiple

et :

a, b = b, a  # échange simultané des valeurs des deux variables

 

La portée d’une variable est l’ensemble du code source où elle est accessible. Il est parfois nécessaire d’aider l’interpréteur à comprendre cette portée, ce qu’illustre le code suivant :

affectation_et_portee_langage_python

En Python, affecter c’est par défaut créer une nouvelle variable, il faut donc indiquer à l’interpréteur quand ce n’est pas le cas.

 

Egalité et identité

Le programmeur Python se pose parfois la question de savoir s’il faut utiliser l’égalité ou l’identité. L’égalité se compare avec == et l’identité avec is.

tableau_observation_conclusion_langage_python

Pour l’anecdote, l’interpréteur standard Cpython, utilise l’adresse d’un objet pour définir son identité (ce que font explicitement les autres langages).

Nous n’enterons pas plus dans le détail du langage dans le cadre de cette présentation – nous l’avons déjà bien assez fait (cet article n’est pas un cours sur Python).

 

Ce que Python n’a pas …

Rapidement quelques manques de Python, qui peuvent frustrer le programmeur qui vient d’autres langages.

 

Les constantes

Python ne marque pas de variables constantes.

 

const int n = 555 ; // langage C++

final int n = 666 ; // Langage JAVA

 

Le « switch case »

Python n’implémente pas le switch case (veto de Guido Van Rossum)

 

switch_case_langage_python

Encapsulation… laxiste

L’encapsulation consiste à cacher les données « intérieures » d’une brique logicielle.

Python pratique une encapsulation permissive. Il ne permet pas de bloquer complètement l’accès aux attributs et aux méthodes d’un objet, mais seulement d’en décourager l’accès :

encapsulation_langage_python

Dans un langage tel que Java et C++, certains attributs seraient marqués « Public » visibles partout ; « Protected » : visibles dans certains cas et pas d’autres ; « Private » : visibles uniquement dans la classe.  Dans ces langages, accéder à des attributs « Private » depuis l’extérieur d’une classe provoque une erreur à la compilation.

Par ailleurs, il ne peut pas y avoir, en Python, de distinction entre un fichier présentant les fonctionnalités (visibles de l’utilisateur d’une classe ou d’une librairie) et un fichier les implémentant (parfois cachées à ce dernier). ADA utilise des fichiers de spécification et d’implémentation. Les langages C et C++ ont des fichiers de prototypes et d’implémentation. Ainsi, un changement d’implémentation limite les compilations nécessaires dans ces langages.

Mais Python ne compile pas...

 

Les pièges de Python à éviter pour les non pythonistes …

Pour mémoire, ces deux pièges classiques (extraits du Python AUSY Awards) fournis à titre d’exemple :

les_pieges_python_a_eviter

Conclusion

Afin de conclure cette série d’articles, nous aimerions revenir de manière synthétique sur les différents aspects que nous avons traités tout au long de ces 5 chapitres.

Au cours de notre analyse, les qualités de ce langage ont été mises en avant :

  • la facilité, la clarté et la vitesse d'apprentissage dans le premier article ;
  • la sureté, la pleine implémentation de l'objet et l'étendue des bibliothèques et outils dans le quatrième article.

Le deuxième article a tenté de justifier les principaux reproches qui lui sont traditionnellement attribués :

  • la scission entre la version deux et la version trois d'une part ;
  • la lenteur d'exécution d'autre part.

Sur ce dernier point, le troisième article a suggéré une panoplie de pistes pour optimiser du code python et le rendre plus rapide.

Ce cinquième et dernier article est entré dans le vif du sujet et a présenté les spécificités du codage en langage python. Les développeurs y trouveront leur compte.

Le lecteur est invité à prolonger sa lecture en approfondissant deux aspects du langage qui n'ont été qu'effleurés dans ces articles :

  • Micropython, un sous ensemble du langage pour l'embarqué, qui réduit l'impact mémoire de l'exécutable.
  • Mypy, une vérification de types sur du code python à base d'annotations, qui permet de fiabiliser son développement (utilisé systématiquement par l’auteur).

Ces deux très récentes nouveautés amènent ce langage sur des plates-bandes sur lesquelles il n’est pas du tout attendu par la communauté des développeurs. Elles restaient jusqu'à il y a quelques années l'apanage des langages classiques (C, C++ et Java etc..).

 

jeremie_lefrancois_consultant_ausy
Jérémie Lefrançois, consultant AUSY depuis 2004, a écrit ses premiers programmes en Basic sur ZX Spectrum en 1982. Diplômé d'un DESS en Informatique en 1989, il a touché à de nombreux langages informatiques à titre personnel ou professionnel. Il aime effectivement explorer et comparer les possibilités des différents langages et plus spécifiquement approfondir Python qui a sa prédilection depuis 2014. C'est donc avec un grand intérêt qu'il surveille les évolutions de ce langage dynamique dont il apprécie la facilité de mise en œuvre.

 

Aussi n’hésitez pas à visiter notre offre Big Data.

Parlons ensemble de vos projets.

contact