Critique du cours vidéo sur Python de Graven

Par Julien Castiaux
Le (Dernière mise à jour le )
Tags : langages python

Le cours intitulé “Apprendre le Python” est un cours proposé sur la chaîne YouTube Gravenilvectuto. Le cours se présente comme un cours pour apprendre les fondamentaux du langage de programmation Python. Il est constitué de 9 vidéos sur le langage d’une moyenne de 13 minutes et d’une vidéo sur la bibliothèque Tkinter de 40 minutes.

Avant tout chose, je (Julien -Dr Lazor- Castiaux) n’ai aucun grief contre Lorenzo, le vidéaste auteur de ce cours. La qualité visuelle des vidéos est supérieure à ce que l’on trouve d’ordinaire, le rythme est correctement soutenu et les exemples sont bien choisis. Les critiques de ce billet ne portent que sur le coté technique, le code source et les explications.

Les défauts du cours

Épisode 1 - Introduction

Contrairement à ce qui est présenté dans la vidéo, un IDE (environnement de développement intégré) n’est pas nécessaire pour développer. Il est d’ailleurs plus commun de croiser des développeurs Python qui utilisent le combo éditeur de texte et terminal du fait de la légèreté du langage. J’ai écrit un billet à ce sujet.

L’ensemble des vidéos sera capturé dans cet environnement. On peut regretter que le vidéaste ne désactive pas les différents linters et correcteurs orthographiques. Les démonstrations sont sans-cesse interrompues par myriade de pop-up de conseil et les différents mots français sont régulièrement soulignés inutilement par le correcteur orthographique.

Lors de l’installation de Python, l’attention du téléspectateur n’est pas attirée sur l’option “Add Python to PATH”, il s’agit d’une case à cocher qui rend accessible les programmes python, py et pip via la ligne de commande. Les débutants ne savent généralement pas comment changer les variables d’environnement sur Windows et se plaignent de ne pas réussir à lancer Python car 'python' n’est pas reconnu en tant que commande interne ou externe, un programme exécutable ou un fichier de commandes..

Le tout premier bout de code montré à l’écran est le suivant :

if __name__ == "__main__":
    print("Hello world")

Si il est commun de voir un if __name__ == "__main__" dans les projets Python, il n’est pas nécessaire et ne présente un intérêt qu’au moment où les modules sont introduits. Il aurait été plus judicieux de ne pas l’introduire pour ne pas rajouter de la complexité inutile.

Le dernier point sera heureusement rectifié dans deux vidéos.

Épisode 2 - Variables

L’épisode est bon dans l’ensemble.

Je regrette néanmoins que la fonction type() n’ait pas été introduite, elle aurait permis de construire la notion de type autrement que par la coloration syntaxique dans pycharm.

Je regrette également que la fonction print n’ait pas été utilisé correctement. La fonction peut prendre plusieurs arguments qui seront tous castés en string et concaténés avec un espace. La solution proposée est de passer via le type str pour convertir le nombre en chaîne de caractères.

Il aurait été judicieux de présenter ces types plus tôt, à savoir les types int(), str(), float() et bool(), et de montrer qu’il peuvent être utilisés pour convertir les types entre eux. Les explications de la conversion en booléen auraient par exemple pu servir de fondation pour l’épisode sur les conditions.

La fonction format sera présentée dans la vidéo suivante pour faciliter l’insertion de données dans les chaînes de caractères.

Épisode 3 - Conditions

Il existe une syntaxe dédiée pour les ternaires depuis Python 2.5(sortie en 2006). La syntaxe est la suivante :

x = true_value if condition else false_value

L’exemple d’expression ternaire présenté dans la vidéo, en plus d’être difficile à lire et obsolète, est faux. Le code montré réalise un accès dans un tuple littéral. Cet accès est possible car le tuple possède deux éléments accessibles aux indices 0 et 1 et que les deux valeurs booléenes ont un équivalent entier : 0 pour False, 1 pour True. Il est dit dans la vidéo que le premier élément sera renvoyé si l’expression est vraie, or c’est le second qui sera renvoyé.

Épisode 4 - Listes

Si l’ensemble des opérations présentées sur les listes sont correctes, l’attention quant à la complexité de ces opérations n’est pas attiré. Bon nombre d’opérations (del, insert, remove) sont en complexité linéaire et non pas constante. Utiliser ces opérations doit donc se faire avec parcimonie.

Pour récupérer le dernier élément d’une liste, la syntaxe len(online_players) - 1 est présentée. Cette syntaxe, bien que correcte, est inutilement verbeuse. Python est en fait capable de directement indexer en partant de la fin en fournissant un indice négatif.

La vidéo présente la bibliothèque statistics pour calculer la moyenne d’une liste. Cette opération est en fait facilement réalisable au moyen des fonctions sum et len.

Les listes seront d’ailleurs les seules structures de données présentées dans la vidéo, pas un mot sur les dictionnaires, les sets ou la bibliothèque collections.

Épisode 5 - Boucles

Le for traditionnellement utilisé dans les langages comme C, Java (for (int x = 0; x < 5; x++)) n’existe pas en Python. Python n’est capable d’itérer que sur des objets itérables (ceux qui définissent les méthodes magiques __iter__ ou/et __next__). range est un de ces objets, bien qu’à l’utilisation il permet de simuler le for classique, il n’en reste pas moins un objet qui implémente ces deux méthodes.

Épisode 6 - Fonctions

Tout de suite après avoir montré comment définir et appeler des fonctions, l’auteur introduit les variables globales. Les variables globales font partie des techniques appelées de mémoire partagée, un ensemble de la mémoire qui est accessible et qui peut être modifié par plusieurs entités à la fois, ici les fonctions. Ces techniques sont connues pour mener à des programmes qui sont difficiles à maintenir, qui présentent plus de bug et qui empêchent l’exécution parallèle. Il est dangereux d’introduire ce mécanisme et on lui préférera systématiquement les fonctions paramétriques.

En programmation orientée objet, lorsqu’une fonction nécessite un contexte pour fonctionner, on préférera utiliser un objet pour sauvegarder ce contexte et écrire une méthode.

L’auteur se trompe lorsqu’il dit qu’il est nécessaire de marquer les variables globales comme globales pour pouvoir y accéder. Il n’y a aucun problème à accéder en lecture (affichage, utilisation) aux variables définies dans le contexte global. Ce mot clé n’est nécessaire que lorsqu’on modifie (affectation) les variables globales.

Épisode 7 - Objets

La vidéo est d’une très grande qualité visuelle, les animations sont réussies, les exemples sont bien choisis et le tout est correctement dynamique. En bref l’auteur réussi à expliquer le mécanisme de l’orienté objet dans une vidéo de seulement 20 minutes, chapeau l’artiste !

Cependant, les bonnes pratiques de développement Python contrastent avec les bonnes pratiques d’autres langages de programmation orientée-objet comme Java, C# ou PHP. En Python, la notion d’accessibilité des variables n’existe pas, pas de mot clé public ou private pour définir la visibilité des attributs ou des méthodes. En réalité il s’agit d’un choix d’implémentation, en Python on accédera directement à l’attribut tant en lecture qu’en écriture sans passer par des méthodes get_xyz() ou set_xyz(). Si exécuter une fonction est nécessaire pour effectuer en traitement avant de renvoyer une valeur, on préférera utiliser une propriété.

Épisodes suivants

Les épisodes 8, 9 et 10 ne présentent pas de problème significatif.

Discussion

Structure de données

Les seules structures de données présentées dans le cours sont les listes et les objets. Python est un langage très riche qui définit une abondance de structures de données qu’il est nécessaire de connaître et de savoir exploiter.

Un ensemble mathématique est une structure de données où les éléments sont ou ne sont pas présents. D’un point de vue informatique, ils peuvent être considérés comme des listes non-ordonnées sans doublons. Ils sont implémentés en Python via l’objet set.

Un dictionnaire est un livre qui associe chaque mot d’une langue à une définition. Il existe aussi des dictionnaires de traduction où chaque mot d’une langue est associé au même mot dans une autre langue. En informatique, un dictionnaire (aussi appelé tableau associatif ou map) est une structure de données qui associe un élément quelconque à un autre élément quelconque. Ils sont implémentés en Python via l’objet dict. Les dictionnaires de base ne sont pas forcément triés (avant Python 3.7), leur équivalent trié est implémenté via collections.OrderedDict.

Un multi-ensemble mathématique est un ensemble où les éléments peuvent exister plusieurs fois. En informatique ils sont généralement implémentés comme un dictionnaire qui associe chaque élément au nombre de fois où il existe. En Python ils sont implémentés via collections.Counter.

Une file ou une queue comme une file à la caisse est une autre structure de données où on ajoute des éléments d’un côté et où on retire les éléments depuis l’autre côté. Si ces opérations sont possible sur des listes classiques, ces opérations sont néanmoins non-optimisées. La structure optimisée pour gérer des files existe via l’objet collections.deque.

Les bibliothèques collections, queue, bisect et heapq regorgent d’autres structures de données qui ne sont pas reprises ici.

Scope Python et variables globales

Ce qu’on appelle le scope est le niveau d’accessibilité des variables, c’est-à-dire quand et comment les variables du programme sont accessibles.

Lorsque Python tente d’accéder à une variable en lecture, il va consulter le scope de la fonction : il commence par consulter les variables locales à la fonction. S’il ne la trouve pas, il va consulter le scope de la fonction supérieure et ainsi de suite jusqu’à arriver au scope dit global qui correspond à la racine du module. Si la variable n’a été trouvée dans aucun scope, une erreur NameError sera remontée à l’utilisateur.

Lorsque Python tente d’accéder à une variable en écriture, il ne consulte que les variables locales, si la variable n’existe pas elle sera créée dans les variables locales même si elle existait dans un scope supérieur. Pour pouvoir modifier une variable d’un scope supérieur il est nécessaire d’utiliser les mots clés global ou nonlocal.

Si ce comportement est celui par défaut c’est qu’il est déconseillé de modifier les variables définies en dehors de sa propre fonction. En effet, pour un programme conséquent, il devient difficile de déterminer quels sont les fonctions qui modifient une variable, le développeur n’a pas d’autre solution que de scanner l’ensemble du programme pour déterminer quelle fonction lit / écrit cette variable ce qui pose des problèmes quant à la compréhension du programme en général.

Un autre problème survient lorsqu’on souhaite paralléliser un traitement sur plusieurs processeurs. Si un processeur lit le contenu d’une variable globale en même temps qu’un autre processeur en change son contenu, on se retrouve dans une situation où l’on ne sait pas ce qui a été lu. La lecture a-t-elle eu lieu avant la modification ? Après ? Ou pire… Pendant ?

Ces problèmes ne se produisent pas lorsqu’on utilise une stratégie d’implémentation claire ou l’utilisation des variables est clairement définie comme pour les fonctions paramétriques ou les objets.

Visibilité des attributs et des méthodes

La visibilité en orienté objet est une notion qui permet de contrôler qui a accès (en lecture, en écriture ou en exécution) aux attributs et aux méthodes d’un objet. Cette notion est implémentée dans nombre de langages orientés objet statiques comme Java, C# et C++. Lorsqu’un attribut est déclaré avec private elle n’est accessible que par l’objet courant, il en va de même pour les méthodes. Au contraire lorsqu’un attribut/une méthode est déclaré public, tout le monde peut y accéder.

Cette notion de visibilité est absente de Python qui préfère faire confiance aux développeurs qu’ils n’aillent pas toucher ce qu’ils ne devraient pas. Ainsi la seule notion qui sépare les attributs, propriétés et méthodes publiques de celles considérées comme “privées” est une convention d’écriture. On préfixera ce qui est privé par un underscore. Le développeur est libre de n’en faire qu’à sa tête de tout de même les utiliser même si c’est déconseillé.

Dans l’industrie il n’y a au final pas de problème à ce que le langage ne définisse pas lui-même de niveau de visibilité. Une équipe constituée de développeurs compétents, que ce soit en Python ou dans un autre langage, est capable de faire la part des choses. Il s’avère même parfois de meilleur goût de ne pas respecter cette convention de visibilité et de tout de même utiliser une variable / méthode “privée”.

Accesseurs et mutateurs

Dans la même lignée qu’il n’y a pas de notion de visibilité en Python, les pratiques de développement Python conseillent de directement modifier les attributs des objets sans passer par des méthodes de contrôle. Dit autrement, l’utilisation systématique d’accesseurs (getters) et de mutateurs (setters) est déconseillé.

Dans l’industrie ceci ne pose en réalité aucun souci, les développeurs qui définissent les interfaces objets sont généralement ceux qui les utiliseront ou qui seront chargés de relire le code des autres développeurs.

Cependant, il est parfois nécessaire d’effectuer des traitements à l’accès ou à la modification des attributs. Pour ce faire, Python a opté pour les propriétés. Une propriété s’utilise comme une variable normale, on récupère la valeur en donnant uniquement le nom de la propriété et on modifie sa valeur comme une simple affectation. La différence est qu’au lieu d’être directement accédée ou modifiée, c’est une fonction de traitement qui sera chargée de renvoyer / modifier la variable.

Via ce mécanisme, il est possible de laisser un attribut “public” (sans underscore en préfixe) et s’il s’avère à l’utilisation qu’une fonction de traitement est nécessaire, il suffit de préfixer l’attribut pour qu’il soit “privé” et de définir au niveau de la classe une propriété du même nom que l’ancien attribut public. Le code qui exploitait l’attribut exploite maintenant la propriété indifféremment.

Cours de Java… en Python…

Si la qualité visuelle des vidéos et la dynamique du cours sont supérieures à bon nombre de cours en ligne (gratuit et payant), le côté technique est quant à lui approximatif. L’approche pédagogique de l’auteur souffre de nombreux biais qui sont communs aux développeurs Java qui s’essaient au langage Python.

Plusieurs arguments penchent en cette faveur :

Conclusion

Le cours est gangrené par les habitudes de développeur Java de l’auteur. Certains concepts essentiels dans le développement Python sont complètements omis comme les dictionnaires, les sets, les générateurs ou la bibliothèque standard. Certains sont approximatifs comme les conditions ou les boucles. Les derniers introduisent des concepts qui vont à l’encontre des bonnes pratiques de développement en Python comme les fonctions ou les objets.

Graven est certainement un très bon développeur Java !