Notions de Python avancées

Découvrez Python plus en profondeur

a marqué ce sujet comme résolu.

Tout le monde se secoue ! :D

J'ai commencé (il y a 56 secondes) la rédaction d'un tutoriel au doux nom de « Notions de Python avancées » et j'ai dans l'objectif de proposer en validation un texte aux petits oignons. Je fais donc appel à votre bonté sans limite pour dénicher le moindre pépin, que ce soit à propos du fond ou de la forme. Vous pourrez consulter la bêta à votre guise à l'adresse suivante :

Merci !

Je me restreins pour l'instant à l'introduction. Pas trop le temps pour la suite.

C'est probablement ce que l'on vous a dit de nombreuses fois, et ce que vous avez constaté en apprenant et pratiquant ce langage. Mais derrière cette simplicité apparante existent un certain nombre de concepts qui forment la puissance de ce langage.

Je ne comprends pas trop l'enchainement des propos, plus précisément le "mais".

En effet, une grande partie des outils sont génériques — tels les appels de fonctions ou les boucles for — c'est à dire qu'ils peuvent s'appliquer à des types différents. Python demande simplement à ces types de respecter une interface en implémentant un certain nombre de méthodes spéciales.

Peut-être pourrais-tu développer cela ? Il me semble que c'est important que comprendre la philosophie de Python, même si tu l'illustres tout au long du tutoriel.

Il me semble intéressant de compléter cette introduction avec deux éléments :

  • Les pré-requis du tutoriel ;
  • Ses objectifs : pourquoi devrais-je le lire ? En l'occurrence, il y a un apport pratique important : on ne se contente pas d'étudier des aspects théoriques du langage, on apprend aussi des fonctionnalités supplémentaires.

Merci. :)

+0 -0

Je ne comprends pas trop l'enchainement des propos, plus précisément le "mais".

Vayel

Il est là pour marquer le contraste. Python est en apparence très simple mais possède des aspects plus complexes en profondeur.

Peut-être pourrais-tu développer cela ? Il me semble que c'est important que comprendre la philosophie de Python, même si tu l'illustres tout au long du tutoriel.

Vayel

Il me semble que tu m'en avais déjà fait la remarque, souhaitant que j'ajoute un paragraphe décrivant le duck typing. Ça ne me semble pas essentiel, je ne pense pas que sa définition apporterait quelque chose d'indispensable, les concepts étant illustrés tout le long du tutoriel.

Il me semble intéressant de compléter cette introduction avec deux éléments :

  • Les pré-requis du tutoriel ;
  • Ses objectifs : pourquoi devrais-je le lire ? En l'occurrence, il y a un apport pratique important : on ne se contente pas d'étudier des aspects théoriques du langage, on apprend aussi des fonctionnalités supplémentaires.

Vayel

Je vais réfléchir à cela.

Il est là pour marquer le contraste. Python est en apparence très simple mais possède des aspects plus complexes en profondeur.

Peut-être ajouter le mot "complexe" du coup ?

Il me semble que tu m'en avais déjà fait la remarque, souhaitant que j'ajoute un paragraphe décrivant le duck typing. Ça ne me semble pas essentiel, je ne pense pas que sa définition apporterait quelque chose d'indispensable, les concepts étant illustrés tout le long du tutoriel.

Pas nécessairement parler de détails du duck typing, mais ajouter un truc du genre "En Python, on s'intéresse au comportement des objets, pas à leur nature". Ce n'est bien entendu qu'une suggestion. :)

+0 -0

Mes retours sur le premier chapitre :

Introduction

Un premier type d'objets très connus sont les objets subscriptables. Ce nom barbare regroupe tous les objets sur lesquels l'opérateur [] peut être utilisé, comme les chaînes de caractères, les listes, les tuples ou les dictionnaires.

Tu pourrais expliciter cette seconde phrase. Concrètement, à quoi cela revient-il d'utiliser cet opérateur ? Si je ne dis pas de bêtise, les subscriptables sont des objets en contenant d'autres, auxquels on peut accéder via l'opérateur [].

Les premiers sont ceux pouvant être indexés avec des nombres entiers, les seconds pouvant l'être avec des slice (voir plus loin).

Je comprends ce que tu veux dire, mais ça fait un peu bizarre de dire qu'on indexe avec des slices.

C'est pas la taille qui compte

Je vous invite à faire des essais en retournant d'autres valeurs (nombres négatifs, flottants, chaînes de caractères) pour observer le comportement.

Et à appeler len sur d'autres types d'objets, comme les dictionnaires.

Objets indexables

RAS.

Les slices

Tu pourrais faire référence au tutoriel sur le sujet publié récemment sur ZdS. :)

TP : Une liste chaînée en Python

La méthode len va pouvoir reprendre une bonne partie du code de get_node, elle itère simplement du début à la fin en comptant le nombre de noeuds.

Ne serait-il pas plus économique d'avoir un attribut size qu'on mettrait à jour à un ajout/suppression de noeud ?

Conclusion

Peut-être pourrais-tu ajouter une conclusion pour résumer les principales idées ?


De manière plus générale, il me semble intéressant de diversifier les exemples. Actuellement, tu emploies majoritairement des listes, et pourrais illustrer les propriétés sur d'autres subscriptables natifs.

+0 -0

En effet, j'ai essayé de varier un peu les exemples, mais ne voulant ensuite pas trop complexifier les exemples, je n'ai utilisé pratiquement que des listes (qui ont l'avantage d'être mutables). Mais je veillerai à ajouter des exemples avec des tuples.

Je vais voir pour les reformulations, je me note ça dans un coin pour y revenir quand j'aurai plus de temps.

Au niveau des slices, j'ai pensé à le faire en voyant le tutoriel paraître, mais n'ai pas encore fait de modification sur le code source depuis.

Pour le TP, la liste implémentée n'a pas pour but d'être performante et encore moins utilisée en conditions réelles. J'ai préféré conserver la structure de données la plus simple possible, quitte à recalculer la taille à chaque appel.

Quant à la conclusion, je suis partagé, il n'y a pas énormément d'éléments différents par chapitre (ceux-ci peuvent de plus se retrouver dans les titres des paragraphes), et le TP permet déjà de synthétiser les informations apportées. Et pour tout te dire, ça complexifierait mon script de génération d'archive compatible ZDS.

A propos des itérables, je crois que tu vas un peu vite, dans le sens où tu sembles supposer que le lecteur connait déjà ce terme. Peut-être pourrais-tu expliquer ce qu'est un itérable et ce que signifie itérer sur un objet, en donner des exemples, puis étudier leur fonctionnement interne.

Une question que je me pose : un itérable est-il nécessairement un subscriptable ?

+0 -0

Pour les callables :

Fonctions, classes et lambdas

Sont callables tous les objets derrière lesquels on peut placer une paire de parenthèses, pour les appeler.

Peut-être pourrais-tu développer cela ? Plus concrètement que signifie "appeler" ? En fait, la question que risque de se poser le lecteur, c'est quand spécifier la méthode __call__ d'une classe.

Paramètres de fonctions

Assignations

Ca n'a pas vraiment de rapport avec les fonctions, donc je ne suis pas sûr qu'il soit judicieux de l'inclure au tutoriel.

Merci. :)

+0 -0

Peut-être pourrais-tu développer cela ? Plus concrètement que signifie "appeler" ? En fait, la question que risque de se poser le lecteur, c'est quand spécifier la méthode __call__ d'une classe.

Vayel

Je le note.

Ca n'a pas vraiment de rapport avec les fonctions, donc je ne suis pas sûr qu'il soit judicieux de l'inclure au tutoriel.

Vayel

Non mais ça a un rapport avec l'opérateur splat, qu'il me semblait essentiel d'aborder, et pour lequel je ne voyais pas consacrer un chapitre entier. Et il reste majoritairement utilisé avec les callable.

Dessine-moi un générateur

Ils sont généralement créés par des fonctions construites à l'aide du mot clef yield. Par abus de langage ces fonctions sont parfois appelées générateurs.

Un exemple pour mieux comprendre cela :

>>> gen = function()

Tu pourrais illustrer l'abus de langage en ajoutant ces lignes :

1
2
>>> function
>>> gen

Altérer un générateur avec send

Au moment où il arrive sur une instruction yield, le générateur se met en pause. Mais à l'itération suivante, l'exécution reprend au niveau de ce même yield.

Je chipote, mais je ne comprends pas trop le "mais".

Dans le cas où vous appelez send, l'exécution reprend, et yield renvoie une valeur, celle passée lors du send.

Le "renvoie une valeur" prête à confusion, puisque pour moi, yield 2 renvoie un valeur, alors que toi tu parles de x = yield.

Pour obtenir le comportement attendu, nous pourrions avancer dans les itérations uniquement si le dernier yield a renvoyé None.

J'ai bloqué là-dessus. Tu pourrais expliciter avec un truc du genre : "si le dernier yield a renvoyé None, c'est-à-dire si on l'a atteint via un next et non un send".

Par une boucle qui exécute des yield tant que ceux-ci ne renvoient pas None.

Le programme qui suit n'est pas simple à comprendre. Comme il est judicieux de laisser le lecteur jouer avec pour se l'approprier, peut-être pourrais-tu simplifier cela en enlevant les caractères ajoutés par l'interpréteur pour qu'on puisse récupérer directement un code fonctionnel ?

Déléguer à un autre générateur avec yield from

À la différence près qu'avec yield from, les paramètres passés avec send sont aussi relégués aux sous-générateurs.

Un exemple ? :)

On peut aussi noter que yield from n'attend pas nécessairement un générateur, mais n'importe quel type d'itérable.

Plus une question qu'une remarque : pourquoi avoir illustré cela dans gen1 et ne pas avoir fait :

1
2
3
4
def gen2():
    yield 0
    yield from [1, 2, 3]
    yield 4

D'autre part, tu pourrais introduire yield from à partir d'un exemple concret (comme tu l'as fait avec la file dans la section précédente) plutôt qu'avec des fonctions sans sens particulier.

Listes et générateurs en intension

Listes en intension

Pourquoi ne pas utiliser un titre de niveau 1 ? Est-ce un problème de conversion GH vers ZdS ?

un peu à la manière de map

map ?

>>> [i + 1 for line in matrix for i in line]

Je chipote, mais i prête à confusion, vu qu'on l'utilise souvent pour désigner l'indice de ligne d'une matrice. Plutôt x ou n ?

De la même manière que pour les listes, nous pouvons définir des générateurs en intension (generator expressions) :

Peut-être pourrais-tu ajouter un passage sur l'utilité de cela ? Jusqu'à maintenant, tu nous as uniquement donné des générateurs "complexes". Là, la question qu'on pourrait se poser, c'est : pourquoi faire un générateur en intension plutôt qu'une liste en intension ?

Ah ben en fait tu dédies une section entière à la question. Peut-être du coup mettre une note à ce niveau, disant que tu réponds plus bas à la question.

Liste ou générateur ?

Il est aussi possible de profiter des avantages de l'un et de l'autre en récupérant une liste en fin de chaîne, par exemple en remplaçant la dernière ligne par :

Veux-tu dire par là que la liste que tu donnes après ne sera pas construite tout de suite, seulement générée au fur et à mesure qu'on récupère ses éléments ? Un exemple d'utilisation ne serait pas de refus.

TP : Un petit creux ?

>>> f = frigo(recettes, ('oeufs', 12), ('farine', 300), ('eau', 100), ('beurre', 250))

Pourquoi avoir choisi une telle interface, et pas :

1
2
3
>>> frigo(recettes, {'oeufs': 12, ...})
# Ou, même si j'ai ma petite idée sur la question :
>>> f = frigo(recettes, [('oeufs', 12), ('farine', 300), ('eau', 100), ('beurre', 250)])

Quand plus aucune recette n'est réalisable, le générateur s'arrête.

Tu pourrais l'ajouter ou le mettre en exercice, mais il serait intéressant de ne pas arrêter le générateur, seulement le mettre en pause, de sorte qu'on puisse toujours ajouter des ingrédients.


Ce chapitre est excellent, l'enchaînement des propos très fluide et tout à fait progressif.

PS : une information que tu pourrais ajouter en introduction du tutoriel, c'est la dépendance entre les sections. Par exemple, pas besoin de connaître les générateurs pour passer aux décorateurs.

+0 -0

PS : une information que tu pourrais ajouter en introduction du tutoriel, c'est la dépendance entre les sections. Par exemple, pas besoin de connaître les générateurs pour passer aux décorateurs.

Trop d'info tue l'info. D'abord ce tutoriel ne s'adresse évidemment pas à des débutants. À partir du moment où les lecteurs ont déjà suivi des cours sur Python (notamment la base + la POO, au minimum), présupposer qu'ils ont une compréhension suffisante du sujet pour utiliser ce cours (qui, en plus, est clairement découpé comme il faut) est tout à fait raisonnable.

De plus, toutes ces notions sont à connaître, et aussi importantes les unes que les autres : soit le lecteur a le niveau pour toutes les comprendre (auquel cas il n'a pas besoin de prioriser les chapitres entre eux), soit il lui manque des compétences et dans ce cas il ne tirera aucun bénéfice de ce cours.

Dans ces conditions, pourquoi alourdir l'intro avec une info qui ne leur servira à rien et qu'ils ne liront probablement même pas 9 fois sur 10 ?

Tu pourrais l'ajouter ou le mettre en exercice, mais il serait intéressant de ne pas arrêter le générateur, seulement le mettre en pause, de sorte qu'on puisse toujours ajouter des ingrédients.

Dans ce cas ce n'est plus un générateur mais une coroutine. Je ne pense pas que ce soit sage de mélanger les deux, notamment parce qu'ils répondent à des besoins différents, et également parce que l'envoi de données à un générateur (bref, l'utilisation des générateurs de Python comme des coroutines), est réellement utilisé de façon anecdotique. En fait, le seul cas sérieux où c'est utilisé, c'est en programmation asynchrone.

De plus, ces échanges (yield vs. send()) sont une arme à double tranchant : si on les utilise dans le mauvais sens (i.e. si on considère le générateur comme le fournisseur de service… et c'est le cas de 100% des générateurs) on se retrouve à utiliser cette fonctionnalité à l'envers, ce qui la rend contre-intuitive et inélegante.

Dans le cas des coroutines, notamment dans le contexte de la programmation asynchrone, c'est le code qui exécute la coroutine qui est fournisseur de service : un yield sert à requérir une ressource ou un service ("faire un appel système"), et send() permet d'envoyer la réponse. Et quand on conçoit le code dans ce sens-là, le fonctionnement devient à la fois léger, intuitif et performant.

Je pense donc que ce serait une grave erreur pédagogique de s'appesantir sur send() dans ce contexte. Les générateurs sont bien trop importants en Python (en sens unique avec yield) pour perdre le lecteur sur ces considérations.

+0 -0

Concernant ta première remarque, je m'imaginais effectivement qu'on aurait besoin de lire le tutoriel morceau par morceau, c'est-à-dire les sections de manière indépendantes. En fait, il est plus probable qu'un lecteur se dise "tiens, je vais apprendre de nouveaux trucs en Python aujourd'hui" et lise l'ensemble.

Pour la suite, d'accord.

+0 -0

Merci encore pour ce retour Vayel, je suis désolé je n'ai pas encore eu le temps de traiter tes premières remarques (enfin, pris le temps, plutôt), mais tout cela est bien noté et sera pris en compte, ma liste s'allonge :p Merci aussi Nohar, c'est très encourageant.

Tu pourrais illustrer l'abus de langage en ajoutant ces lignes :

1
2
>>> function
>>> gen

Vayel

C'est une bonne idée, oui, je l'ajouterai.

Je chipote, mais je ne comprends pas trop le "mais". […] Le "renvoie une valeur" prête à confusion, puisque pour moi, yield 2 renvoie un valeur, alors que toi tu parles de x = yield. […] J'ai bloqué là-dessus. Tu pourrais expliciter avec un truc du genre : "si le dernier yield a renvoyé None, c'est-à-dire si on l'a atteint via un next et non un send".

Vayel

Je vais essayer de revoir ces différentes tournures de phrases.

Le programme qui suit n'est pas simple à comprendre. Comme il est judicieux de laisser le lecteur jouer avec pour se l'approprier, peut-être pourrais-tu simplifier cela en enlevant les caractères ajoutés par l'interpréteur pour qu'on puisse récupérer directement un code fonctionnel ?

Vayel

Oui, je ferai ça. Et peut-être expliciter un peu plus ce « bricolage ».

Un exemple ? :) […] Plus une question qu'une remarque : pourquoi avoir illustré cela dans gen1 et ne pas avoir fait :

1
2
3
4
def gen2():
    yield 0
    yield from [1, 2, 3]
    yield 4

D'autre part, tu pourrais introduire yield from à partir d'un exemple concret (comme tu l'as fait avec la file dans la section précédente) plutôt qu'avec des fonctions sans sens particulier.

Vayel

Je pense réutiliser la queue vue précédemment, et peut-être permettre une concaténation de queues. On verra ainsi bien une utilisation concrète (itertools.chain), et la délégation du send en action.

Pourquoi ne pas utiliser un titre de niveau 1 ? Est-ce un problème de conversion GH vers ZdS ?

Vayel

En effet, dans mes sources, les titres de niveau 1 correspondent au titre de chapitres, ceux de niveau 2 aux titres de sections, donc les suivants sont les titres de niveau 3. Je peux faire en sorte que mon script modifie cela, je verrai.

Je chipote, mais i prête à confusion, vu qu'on l'utilise souvent pour désigner l'indice de ligne d'une matrice. Plutôt x ou n ?

Vayel

J'ai l'habitude d'utiliser i pour une variable entière parcourue sur une liste, là où n représente plutôt pour moi la taille de la liste, et je réserve x aux manipulations de coordonnées.

Veux-tu dire par là que la liste que tu donnes après ne sera pas construite tout de suite, seulement générée au fur et à mesure qu'on récupère ses éléments ? Un exemple d'utilisation ne serait pas de refus.

Vayel

Je vais voir ce que je peux faire.

Pourquoi avoir choisi une telle interface, et pas :

1
2
3
>>> frigo(recettes, {'oeufs': 12, ...})
# Ou, même si j'ai ma petite idée sur la question :
>>> f = frigo(recettes, [('oeufs', 12), ('farine', 300), ('eau', 100), ('beurre', 250)])

Vayel

Je n'ai plus précisément en tête les raisons de ce choix, probablement parce que c'est ainsi que la syntaxe est la plus claire. Si je change, je pense que ce serait au profit d'arguments nommés.

Tu pourrais l'ajouter ou le mettre en exercice, mais il serait intéressant de ne pas arrêter le générateur, seulement le mettre en pause, de sorte qu'on puisse toujours ajouter des ingrédients.

Vayel

Personnellement, je n'aime pas cet exercice du frigo. C'est tiré par les cheveux et ça ne représente rien de concret. Je ne pense pas qu'ajouter une telle fonctionnalité serve à quelque chose.

PS : une information que tu pourrais ajouter en introduction du tutoriel, c'est la dépendance entre les sections. Par exemple, pas besoin de connaître les générateurs pour passer aux décorateurs.

Vayel

Si je me fie à ma manière de parcourir un tutoriel, je ne juge pas cette information vraiment utile. J'ai plutôt tendance à sauter directement à la partie qui m'intéresse, quitte à lire ensuite les précédentes si j'en éprouve l'intérêt ou le besoin. Et il n'y a ici pas tellement de dépendances entre les concepts, juste des exemples réutilisés d'un chapitre à l'autre.

Personnellement, je n'aime pas cet exercice du frigo. C'est tiré par les cheveux et ça ne représente rien de concret. Je ne pense pas qu'ajouter une telle fonctionnalité serve à quelque chose.

entwanne

Les exemples qui se prêtent bien à yield et yield from sont généralement de nature récursive : parcourir un arbre, par exemple.

+0 -0

Bonjour les agrumes !

La bêta tutoriel « Notions de Python avancées » a été mise à jour et coule sa pulpe à l'adresse suivante :

Merci d'avance pour vos commentaires.


Bonne nouvelle, je viens de consacrer un peu de mon temps à la correction des différents points cités sur le sujet, merci encore Vayel pour toutes ces remarques. La plus grosse modification concerne le chapitre sur les subscriptables, qui est maintenant étendu aux conteneurs, avec l'ajout d'une section consacrée à l'opérateur in.

Aussi, je me posais une question quant aux illustrations : le tutoriel en est pour le moment totalement dépourvu. Pensez-vous que cela soit quelque chose de nécessaire ?

Bonjour les agrumes !

La bêta tutoriel « Notions de Python avancées » a été mise à jour et coule sa pulpe à l'adresse suivante :

Merci d'avance pour vos commentaires.


Je viens d'ajouter des conclusions pour chaque chapitre, ainsi qu'un logo. Après avoir survolé vite fait, je remarque que les tournures de phrase de ces conclusions ne sont pas toujours pertinentes, et parfois répétitives, donc je referai une passe.

Ce sujet est verrouillé.