## page was renamed from Projet/SemaineTech2011/Ateliers/ProgrammationPython/Conversation ## page was renamed from Projet/SemaineTech/Ateliers/ProgrammationPython/Conversation <> = Lundi = {{{#!highlight irc (14:00:29) olivier.larcheveque: On reprends avec la présentation de Davin sur la programmation Python https://wiki.auf.org/wikiteki/Projet/SemaineTech/Ateliers/ProgrammationPython (14:00:32) semainetech@reunion.auf.org: willy a changé le sujet en : ☞ Le salon de la Semaine Tech ☜ Atelier "Programmation python" https://wiki.auf.org/wikiteki/Projet/SemaineTech/Ateliers/ProgrammationPython (14:00:46) davin.baragiotta: ---------- DÉBUT : Atelier : Programmation Python ---------------- (14:00:51) davin.baragiotta: INTRODUCTION (14:00:55) davin.baragiotta: Bonjour à tous! (14:01:00) davin.baragiotta: La documentation complète de Python se trouve ici : http://www.python.org/doc/ (14:01:07) davin.baragiotta: Vous y trouverez notamment un tutoriel (que j'aurais aimé suivre mais c'est trop long) : http://docs.python.org/tutorial/ (14:01:16) davin.baragiotta: ... et aussi la documentation de la librarie (bibliothèque) standard venant avec le langage (on y reviendra) : http://docs.python.org/library/ (14:01:20) davin.baragiotta: Aujourd'hui, on va programmer en Python. (14:01:21) davin.baragiotta: :D (14:01:27) davin.baragiotta: Dans cet atelier : * on va coder dans l'interpréteur et explorer les bases du langage * on va écrire un script qu'on fera exécuter par l'interpréteur * on va explorer du vrai code et modifier celui-ci pour créer un bot jabber!!!! (14:01:39) davin.baragiotta: ENVIRONNEMENT (14:01:50) davin.baragiotta: Vous devriez avoir un éditeur texte sous la main, configuré comme demandé dans la page wiki. (14:01:57) davin.baragiotta: Vous devriez avoir installé l'interpréteur ipython. (14:02:05) davin.baragiotta: Vous devriez avoir téléchargé l'archive code.tar.gz : https://wiki.auf.org/wikiteki/Projet/SemaineTech/Ateliers/ProgrammationPython?action=AttachFile&do=view&target=code.tar.gz (14:02:13) davin.baragiotta: Ouvrez un terminal et déplacez vous dans le répertoire où vous avez extrait l'archive : cd code/ (14:02:21) davin.baragiotta: C'est parti! (14:02:36) davin.baragiotta: INTERPRÉTEUR (14:03:00) davin.baragiotta: L'interpréteur python par défaut se lance avec la commande: python (14:03:06) davin.baragiotta: il vient avec le langage.... (14:03:14) davin.baragiotta: mais nous on va travailler avec ipython qui se lance avec la commande.... : ipython (14:03:21) davin.baragiotta: ipython permet une introspection plus aisée (on y reviendra) (14:03:28) davin.baragiotta: lancez dans votre terminal ipython : ipython (14:03:50) davin.baragiotta: Quand on code en Python, on utilise l'interpréteur pour explorer interactivement le code. (14:03:57) davin.baragiotta: Mais l'édition directement dans l'interpréteur peut être pénible. (14:04:04) davin.baragiotta: C'est pourquoi nous mettons le "code qui marche" et éditons le code plus complexe dans un script. (14:04:14) davin.baragiotta: C'est ce que nous ferons après. Explorons d'abord dans l'interpréteur. (14:04:19) davin.baragiotta: PYTHON : QUELQUES BASES (14:04:28) davin.baragiotta: Toute programmation repose sur des conventions syntaxiques... En Python, on a de la chance, la syntaxe est très simple et intuitive. (14:04:37) davin.baragiotta: Les commentaires se font avec # : # ceci est un commentaire (14:04:44) davin.baragiotta: Les variables se déclarent sans $ devant ou autre fioriture. (14:04:52) davin.baragiotta: Les instructions ne se terminent pas avec des ; (14:05:00) davin.baragiotta: Les blocs de code ne sont pas structurés avec des { } mais par l'indentation. (14:05:09) davin.baragiotta: Les conventions pour le style de code en Python sont définies dans la PEP 8 (genre de norme) : http://www.python.org/dev/peps/pep-0008/ (14:05:24) davin.baragiotta: PYTHON : QUELQUES BASES : LES TYPES (14:05:34) davin.baragiotta: Voici les principaux types de base fournis avec le langage : n = None # NoneType : type spécial voulant dire... rien b = True # bool : booléen... True ou False n'oubliez pas les majuscules i = 15 # int : entier f = 15.5 # float : décimal s = "chaine" # str : chaine de caractère, instancié avec "" ou '' u = u"chaîne" # unicode : chaîne de caractère unicode, instancié avec u"" ou u'' l = [] # list : liste d'objets t = () # tuple : liste immuable d'objets d = {} # dict : dictionnaire de données (14:06:10) davin.baragiotta: PYTHON : QUELQUES BASES : OBJETS ET NAMESPACES (14:06:20) davin.baragiotta: En Python, tout est un "objet". Concrètement, ça veut dire que chaque "chose" qu'on manipule a: * un nom * des attributs (variables) accessibles "derrière" * des méthodes (fonctions) accessibles "derrière" (14:06:34) davin.baragiotta: Les attributs et méthodes ont des noms aussi. (14:06:41) davin.baragiotta: Mais ils se trouvent dans l' "espace de nom" de l'objet. (14:06:48) davin.baragiotta: Ils sont accessibles en utilisant un point pour séparer les noms : objet.attribut objet.methode() (14:06:58) davin.baragiotta: On peut chaîner les noms avec les points : objet.attribut.methode_attribut() (14:07:07) davin.baragiotta: Assez la théorie. Codons. (14:07:08) davin.baragiotta: :D (14:07:15) davin.baragiotta: INTROSPECTION (14:07:22) davin.baragiotta: Dans votre interpréteur déclarez une chaîne de caractères unicode : prenom = u"Davin" (14:07:42) davin.baragiotta: L'idée de l'introspection est de : voir en interrogeant l'objet ce qu'on peut faire avec (14:07:51) davin.baragiotta: ipython nous offre une façon simple de regarder ça (14:07:58) davin.baragiotta: après le nom de votre variable... tapez '.' puis 'tab' prenom. [+ tab] (14:08:10) davin.baragiotta: vous voyez tous les noms (attributs et méthodes) accessibles sur cet objet unicode (14:08:17) davin.baragiotta: ('enter' pour voir plus, 'q' pour sortir de la liste) (14:08:29) davin.baragiotta: tous les noms qui commencent avec __* sont des noms systèmes. (14:08:38) davin.baragiotta: Le nom seul n'est parfois pas suffisant pour savoir quoi faire avec... (14:08:46) davin.baragiotta: on va interroger l'interpréteur pour qu'il nous donne de l'info sur le nom (14:08:59) davin.baragiotta: derrière n'importe quel nom, on peut mettre '?' et taper 'enter' prenom.upper? (14:09:12) davin.baragiotta: ooooouuah! la partie docstring est carrément la documentation de la méthode! (14:09:14) davin.baragiotta: :D (14:09:52) davin.baragiotta: si c'est la méthode qu'on veut (14:09:52) davin.baragiotta: on l'exécute : prenom.upper() (14:10:10) davin.baragiotta: note : l'objet prenom n'en est pas modifié... (14:10:14) davin.baragiotta: tapez : prenom (14:10:31) davin.baragiotta: Les fonctions built-in Python suivantes servent aussi à l'introspection : type(objet) # retourne le type de l'objet dir(objet) # retourne les noms derrière l'objet help(objet) # retourne l'aide callable(objet) # dit si un objet est appelable, exécutable... isinstance(objet, Type) # teste le classe (ou type) d'un objet ... (14:10:46) davin.baragiotta: exemple : type(prenom) (14:11:19) davin.baragiotta: l'objet prenom est bien de type unicode car on l'a instancié avec u"" (un u devant) (14:11:23) davin.baragiotta: Des questions? (14:11:47) olivier.larcheveque: non pas de questions (14:12:01) davin.baragiotta: ok cool (14:12:30) davin.baragiotta: on peut jouer un peu dans l'interpréteur avant de regarder un script (14:12:49) davin.baragiotta: nom = u"Baragiotta" (14:12:53) davin.baragiotta: l = [] (14:12:59) davin.baragiotta: l. + tab (14:13:15) davin.baragiotta: on utiliserait quoi pour ajouter notre nom et prenom à la liste vide? (14:13:37) davin.baragiotta: l.append? (14:13:45) davin.baragiotta: l.append(prenom) (14:13:50) davin.baragiotta: l.append(nom) (14:13:55) davin.baragiotta: tapez : l (14:14:18) davin.baragiotta: si vous voulez itérer : for (14:14:33) davin.baragiotta: for item in liste : print item.upper() (14:14:37) davin.baragiotta: ach (14:14:42) davin.baragiotta: for item in liste : print item.upper() (14:15:11) davin.baragiotta: Questions? (14:15:14) olivier.larcheveque: REMARQUE de JC sur l'encodage : attention au piège de la saisie Unicode dans iPython : prenom = u"Céline" ne marchera pas, ça vous donnera la chaîne Unicode u'C\xc3\xa9line', qui est incorrecte (c'est de l'UTF-8), au lieu de u'C\xe9line', qui est ce qu'on attendait ; pour obtenir le résultat correct en direct dans iPython il faut taper ceci : prenom = unicode("Céline", 'utf-8') (14:15:44) olivier.larcheveque: autrement pas de questions (14:16:04) davin.baragiotta: oui, pour les questions d'encodage, python est mieux... mais en codant ce qu'on a fait dans un script et en l'exécutant... on devrait pas avoir à passer par unicode() (14:16:06) davin.baragiotta: okay (14:16:17) davin.baragiotta: SCRIPTS (14:16:19) olivier.larcheveque: les apprenants te font remarquer une erreur ;) (14:16:23) davin.baragiotta: ah? (14:16:36) olivier.larcheveque: c'est correct! ;) (14:16:37) davin.baragiotta: mais vous mentez, c'est mal... (14:16:40) davin.baragiotta: l = [] (14:16:41) davin.baragiotta: ;) (14:16:48) davin.baragiotta: (09:12:53) davin.baragiotta: l = [] (14:16:50) davin.baragiotta: :D (14:17:24) davin.baragiotta: SCRIPTS (14:17:28) davin.baragiotta: Comme on le disait, c'est pas top coder dans l'interpréteur. Alors on va faire un script. (14:17:33) davin.baragiotta: il s'agit d'inclure du code dans un fichier et demander à l'interpréteur d'exécuter le code (14:17:41) davin.baragiotta: ouvrez dans un éditeur le fichier script.py : code/script.py (14:17:49) davin.baragiotta: la première ligne s'appelle shebang (14:17:55) davin.baragiotta: c'est pour dire au système que ce qui suit est du python (14:18:03) davin.baragiotta: ligne 2 : dit que c'est encodé en UTF-8 : ça nous permet d'écrire avec caractères accentués (notamment texte et commentaires) (14:18:13) davin.baragiotta: ligne 7 : un peu obscure, cette ligne se lit comme ça : si le nom de ce script est __main__ (ce qui est son nom par défaut quand lancé dans un interpréteur) alors exécute ce qui suit (14:18:25) davin.baragiotta: c'est là qu'on met les instructions du script pour les cas où on le lance avec un interpréteur (14:18:47) davin.baragiotta: lançons le script avec ipython : run script ou run script.py (14:18:58) davin.baragiotta: (avec l'interpréteur normal ce serait juste : python script.py ) (14:19:33) davin.baragiotta: Voilà pour la partie théorique... il resterait beaucoup à dire (14:19:39) davin.baragiotta: mais je vous laisse explorer par vous même (14:19:45) davin.baragiotta: les if... par exemple... (14:20:03) davin.baragiotta: on peut regarder rapidement la syntaxe de la fonction (14:20:10) davin.baragiotta: ligne 4 de script.py (14:20:33) davin.baragiotta: en fait ce qui est remarquable ici = pas d'accolades, pas de ; (14:20:44) davin.baragiotta: une indentation de 4 esapces (pas de tabs dans vos scripts svp ;) ) (14:21:07) davin.baragiotta: on peut faire plein de trucs avec les types de bases... mais c'est facile à explorer (14:21:18) davin.baragiotta: C'est la semaine tech, jouons maintenant! ;) (14:21:25) davin.baragiotta: Regardons du code plus intense (14:21:26) davin.baragiotta: ;) (14:21:33) davin.baragiotta: MODULES : BOT JABBER (14:21:44) davin.baragiotta: l'idée générale = créer un robot qui puisse être dans un salon jabber et interagir avec les users connectés (14:21:56) davin.baragiotta: ah, des questions? (14:23:09) davin.baragiotta: ouvrez et éditez le fichier bot/conf.py.edit et sauvegardez le dans : bot/conf.py (14:23:10) davin.baragiotta: assurez-vous que : CHATROOM = "test@reunion.auf.org" (14:23:10) davin.baragiotta: si vous n'avez pas de username et de password pour votre bot : (14:23:10) davin.baragiotta: https://register.jabber.org/ (14:24:10) davin.baragiotta: tout le monde a créé un conf.py dans le répertoire bot? (14:24:50) davin.baragiotta: vous pouvez vous rendre dans le salon test (14:25:32) olivier.larcheveque: QUESTION: quel est l'intérêt de mettre un if __name__=='__main__' et de mettre son code au lieu de l'inclure une fois sans mettre cette ligne ? REPONSE : on peut utiliser le fichier comme un module dans avoir à lancer le script (14:25:40) olivier.larcheveque: ça permet d'importer des parties du script sans obligatoirement en exécuter le contenu (14:26:25) davin.baragiotta: ok je vais lancer mon bot (14:26:51) olivier.larcheveque: REMARQUE : davin.baragiotta n'a pas explicité comment on se rend dans le salon après avoir modifié la config (14:27:04) davin.baragiotta: ok mon bot s'appelle barabotta et est présent dans le salon test (14:27:09) davin.baragiotta: pour lancer votre bot : (14:27:16) davin.baragiotta: run bot/bot.py (14:28:04) davin.baragiotta: donc maintenant, vous (comme user) devriez être dans le salon test (de la même façon que vous avez fait pour vous rendre ici) ;) (14:28:07) davin.baragiotta: et votre bot aussi (14:28:29) davin.baragiotta: cool, y'a plein de bot (14:29:00) davin.baragiotta: ok je vais montrer des commandes du bot et on regardera le code qui l'exécute avant que vous le modifiez (14:29:01) davin.baragiotta: :D (14:31:29) davin.baragiotta: on a regardé quelques commandes du bot (14:31:33) davin.baragiotta: regardons les sources (14:31:50) davin.baragiotta: ouvrez dans un éditeur le fichier bot/bot.py (14:32:38) davin.baragiotta: en haut du fichier, on connait maintenant : shebang et encoding (14:32:39) davin.baragiotta: cool (14:32:45) davin.baragiotta: lignes 4 à 8 (14:32:50) davin.baragiotta: mesdames et messieurs... (14:32:57) davin.baragiotta: c'est toute la puissance de Python devant vous (14:33:07) davin.baragiotta: les import !!!! (14:33:17) davin.baragiotta: le but du jeu, quand on code... c'est de ne pas réinventer la roue (14:33:34) davin.baragiotta: pour ce faire, on importe le code qui règle déjà nos problèmes... (14:33:41) davin.baragiotta: ligne 4 (14:33:51) davin.baragiotta: from datetime import datetime (14:34:19) davin.baragiotta: ça veut dire que : du module (standard) datetime on veut importer le nom datetime (14:34:40) davin.baragiotta: en gros : datetime.datetime (14:34:56) davin.baragiotta: vous voulez en savoir plus sur ce module? (14:35:07) davin.baragiotta: voir la doc officielle : http://docs.python.org/library/ (14:35:16) davin.baragiotta: cherchez datetime (14:35:21) davin.baragiotta: http://docs.python.org/library/datetime.html (14:35:23) davin.baragiotta: lisez ;) (14:35:44) davin.baragiotta: sinon, importez le module dans interpréteur et introspectez!!! : import datetime (14:35:51) davin.baragiotta: continuons (14:36:08) davin.baragiotta: import re = import du module d'expressions régulière (14:36:24) davin.baragiotta: en fait... tous les messages échangés nous arrivent comme des lignes de texte.... (14:36:47) davin.baragiotta: on veut pouvoir chercher des mots dans les lignes pour trouver les commandes données au bot! (14:36:59) davin.baragiotta: re = regex = regular expression (14:37:09) davin.baragiotta: http://docs.python.org/library/re.html (14:37:17) davin.baragiotta: un petit monde en soit au niveau de la syntaxe (14:37:20) davin.baragiotta: on continue (14:37:39) davin.baragiotta: après mes imports de la librairie standard (14:37:59) davin.baragiotta: j'importe les modules particuliers installés dans mon système... (14:38:22) davin.baragiotta: jabberbot est accessible car vous l'avez installé via un paquet Debian avant l'atelier (14:38:33) davin.baragiotta: il est accessible par son nom de module python (14:39:03) davin.baragiotta: y'a 2 façons d'importer : from... import (14:39:25) davin.baragiotta: import re , par exemple, charge tout le module re (14:39:53) davin.baragiotta: ils sont accessible dans le code en préfixant les objets du module par le nom du module (14:39:58) davin.baragiotta: ex.: ligne 38 (14:40:08) davin.baragiotta: j'utilise compile de re en faisant re.compile (14:40:13) davin.baragiotta: from : (14:40:32) davin.baragiotta: charge seulement des noms spécifiques... accessibles directement (14:40:52) davin.baragiotta: ex.: MUCJabberBot ligne 10 (14:40:55) davin.baragiotta: chargé à la ligne 8 (14:41:09) davin.baragiotta: ligne 8 : muc vient d'où? (14:41:16) davin.baragiotta: il vient de *mon* module bot (14:41:28) davin.baragiotta: le répertoire bot est lui même un module (14:41:53) davin.baragiotta: on l'a pas installé sur notre système... mais il est accessible dans le contexte (14:42:08) davin.baragiotta: pour importer des éléments d'un autre script (14:42:16) davin.baragiotta: créez un module (14:42:18) davin.baragiotta: comment ? (14:42:31) davin.baragiotta: en mettant un fichier __init__.py dans un répertoire (14:42:43) davin.baragiotta: grace à bot/__init__.py (14:43:01) davin.baragiotta: on peut importer du contenu de ce répertoire (14:43:06) davin.baragiotta: preuve (14:43:20) davin.baragiotta: ligne 8 importe du fichier bot/muc.py (14:43:25) davin.baragiotta: Des questions sur l'import? (14:43:32) davin.baragiotta: après on personnalise votre bot (14:43:50) davin.baragiotta: Questions? (14:44:15) olivier.larcheveque: Non pas de questions (14:44:40) olivier.larcheveque: (quelques petits soucis avec les versions de jabberbot) (14:45:23) davin.baragiotta: ok puor ceux qui ont un bot qui marche, ont va le personnaliser (14:45:58) davin.baragiotta: copiez la ligne 38 et collez dessous en ligne 39 (14:46:17) davin.baragiotta: modifiez le mot dans le "bière" dans le regex (14:46:43) davin.baragiotta: exemple : self.re_biere = re.compile(u'.*(?Pbi[eè]re).*') self.re_biere = re.compile(u'.*(?Phourra).*') (14:46:53) willy: QUESTION: que signifie exactement @botcmd ? (14:46:58) davin.baragiotta: avec ça, un surveille le mot hourra dans les chaine (14:47:07) davin.baragiotta: botcmd est un décorateur (14:47:15) davin.baragiotta: une fonction qui s'exécute avant une fonction (14:47:30) davin.baragiotta: mais globalement, ici, ça veut dire que la fonction qui suit (14:47:34) davin.baragiotta: est une "commande du bot" (14:47:48) davin.baragiotta: donc en tapant ce mot : la fonction codée s'exécute (14:48:00) davin.baragiotta: il y a 2 approches pour l'interaction du bot : (14:48:17) davin.baragiotta: une commande ou il cherche un mot par regex (14:48:29) davin.baragiotta: ici on va personnaliser en mettant un mot dans regex (14:48:47) davin.baragiotta: je continue mon exemple perso avec hourra (mettez ce que vous voulez pour vous) (14:49:00) davin.baragiotta: donc on a ligne 39 cloné et modifié la ligne 38 (14:49:31) davin.baragiotta: ligne 50 et 51 : copier et coller dessous (14:50:05) davin.baragiotta: ah zut... il faut modifier le nom d'attribut en ligne 39 aussi : (14:50:14) davin.baragiotta: 38 et 39 : self.re_biere = re.compile(u'.*(?Pbi[eè]re).*') self.re_hourra = re.compile(u'.*(?Phourra).*') (14:50:31) davin.baragiotta: 50 et 51 : if self.re_biere.match(message): self.biere(mess) if self.re_hourra.match(message): self.hourra(mess) (14:50:57) davin.baragiotta: avec ça on vient de dire si le message match le pattern (regex) écrit dans self.re_hourra (14:51:14) davin.baragiotta: alors on exécute la méthode (fonction) hourra... (14:51:15) davin.baragiotta: mais elle n'existe pas encore : créons là (14:51:35) davin.baragiotta: copipez-collez def biere au grand complet, jusqu'au return (14:51:48) davin.baragiotta: et modifier tout pour personnaliser (14:52:21) davin.baragiotta: en fait, faut juste modifier le nom de la méthode (derrière le def) et éditer la réponse (14:52:22) davin.baragiotta: ex.: (14:52:38) davin.baragiotta: def biere(self, mess): """Aux messages relatifs à la bière, choisit une de ses réponses prédéfinies""" username = self.get_sender_username(mess) if username != self.username and username != self.nickname: response = 'St-Ambroise!' self.send_simple_reply(mess, response) def hourra(self, mess): """Aux messages relatifs à la bière, choisit une de ses réponses prédéfinies""" username = self.get_sender_username(mess) if username != self.username and username != self.nickname: response = 'Non... pas hourra.' self.send_simple_reply(mess, response) (14:52:57) davin.baragiotta: pour tester le nouveau comportement du bot : (14:53:11) davin.baragiotta: fermer dans votre terminal le bot en core : Ctrl + C (14:53:13) davin.baragiotta: *en cours (14:53:27) davin.baragiotta: et attendre qu'il sorte du salon test (peut être un peu long) (14:53:42) davin.baragiotta: puis après relancer le bot : run bot/bot.py (14:55:29) davin.baragiotta: bon j'attends toujours la déconnexion de mon bot barabotta... :( (14:56:34) olivier.larcheveque: (Il reste 5min si les gens ont des questions) (14:56:36) progfou: reviens avec une autre "resource" pour ne pas perdre de temps... (14:56:38) davin.baragiotta: vous pouvez personnaliser, évidemment, en modifiant seulement les lignes de réponse pour les méthodes biere et sieste (14:58:43) davin.baragiotta: ok (14:58:50) davin.baragiotta: vous pouvez essayez mon hourra ;) (14:58:54) davin.baragiotta: questions? MR 20110829T13:57:54Z 000 fermez les bots MR 20110829T13:57:58Z 000 avec ctrl + c MR 20110829T13:58:03Z 000 si boucle sans fin MR 20110829T13:58:05Z 000 ;) MR 20110829T13:58:44Z 000 -----------------------FIN : ATELIER : programmation Python ----------------------- MI 20110829T14:00:17Z 002 willy has set the topic to: ☞ Le salon de la Semaine Tech ☜ Atelier "Django" https://wiki.auf.org/wikiteki/Projet/SemaineTech/Ateliers/Django MR 20110829T14:03:21Z 000 petit interlude entre ateliers MR 20110829T14:03:34Z 000 on a eu dans test... là où vivent encore nos bots MR 20110829T14:03:37Z 000 un boucle sans fin MR 20110829T14:04:12Z 000 ça arrive quand un bot réagit à un mot... et retourne ce même MR 20110829T14:04:23Z 000 on peut éviter en vérifiant qu'on ne se répond pas à soit MR 20110829T14:04:24Z 000 mais MR 20110829T14:04:54Z 000 si un bot réagit à hourra et répondant hourra... et qu'un autre bot fait pareil : réagit à hourra en disant hourra MR 20110829T14:05:14Z 000 même si chaque bot s'ignore soit même... ben... MR 20110829T14:05:31Z 000 il répond à l'autre qui lui répond qui fait répodnre l'autre qui... MR 20110829T14:05:36Z 000 un boucle *entre* les bot MR 20110829T14:05:57Z 000 en gros... c'est en testant qu'on blinde son code ;) MR 20110829T14:06:01Z 000 fin de l'interlude MR 20110829T14:06:45Z 001 progfou faisant remarquer : (10:06:13) progfou: et en fait même, plus généralement, qu'un bot ne devrait pas traiter des messages sans préfixe déterminé (son nom, un '!', ...) MR 20110829T14:06:57Z 000 c'est une solution... mais ça enlève son côté humain ;) MR 20110829T14:07:13Z 000 ouaip, ça en fait un bot ;-) MR 20110829T14:07:18Z 000 :D MR 20110829T14:08:17Z 000 Olivier, si tu es prêt n'attendons pas, ce n'est pas bon de s'éloigner des horaires (certaines personnes peuvent avoir prévu autre chose après, à l'heure pile, et d'autres auront envie de dormir, suivez mon regard...) MR 20110829T14:08:54Z 000 ok on y va }}} = Jeudi = {{{#!highlight irc (13:10:26) davin.baragiotta: ---------- DÉBUT : Atelier : Programmation Python ---------------- (13:10:35) davin.baragiotta: ### REMARQUE : cet atelier présente le même contenu que l'atelier de lundi dernier ### Si vous avez participé lundi et souhaitez aller plus loin, ### un exercice vous a été fourni par courriel sur : discussion-technique@auf.org ### Sujet : [Semaine tech] Atelier : Programmation Python : EXERCICE ### ### Les liens vers les solutionnaires seront donnés ici, sous la forme : ### EXERCICE : solution ... (13:11:57) davin.baragiotta: INTRODUCTION (13:12:02) davin.baragiotta: Bonjour à tous! (13:12:08) davin.baragiotta: La documentation complète de Python se trouve ici : http://www.python.org/doc/ (13:12:17) davin.baragiotta: Vous y trouverez notamment un tutoriel (que j'aurais aimé suivre mais c'est trop long) : http://docs.python.org/tutorial/ (13:12:27) davin.baragiotta: ... et aussi la documentation de la librarie (bibliothèque) standard venant avec le langage (on y reviendra) : http://docs.python.org/library/ (13:12:38) davin.baragiotta: Aujourd'hui, on va programmer en Python. (13:12:40) davin.baragiotta: :D (13:12:47) davin.baragiotta: Dans cet atelier : * on va coder dans l'interpréteur et explorer les bases du langage * on va écrire un script qu'on fera exécuter par l'interpréteur * on va explorer du vrai code et modifier celui-ci pour créer un bot jabber!!!! (13:13:00) davin.baragiotta: ENVIRONNEMENT (13:13:06) davin.baragiotta: Vous devriez avoir un éditeur texte sous la main, configuré comme demandé dans la page wiki. (13:13:12) davin.baragiotta: Vous devriez avoir installé l'interpréteur ipython. (13:13:22) davin.baragiotta: Vous devriez avoir téléchargé l'archive code.tar.gz : https://wiki.auf.org/wikiteki/Projet/SemaineTech/Ateliers/ProgrammationPython?action=AttachFile&do=view&target=code.tar.gz (13:13:28) davin.baragiotta: Ouvrez un terminal et déplacez vous dans le répertoire où vous avez extrait l'archive : cd code/ (13:13:35) davin.baragiotta: C'est parti! (13:13:41) davin.baragiotta: INTERPRÉTEUR (13:13:49) davin.baragiotta: L'interpréteur python par défaut se lance avec la commande: python (13:13:56) davin.baragiotta: il vient avec le langage.... (13:14:01) davin.baragiotta: mais nous on va travailler avec ipython qui se lance avec la commande.... : ipython (13:14:06) davin.baragiotta: ipython permet une introspection plus aisée (on y reviendra) (13:14:12) davin.baragiotta: lancez dans votre terminal ipython : ipython (13:14:18) davin.baragiotta: Quand on code en Python, on utilise l'interpréteur pour explorer interactivement le code. (13:14:24) davin.baragiotta: Mais l'édition directement dans l'interpréteur peut être pénible. (13:14:31) davin.baragiotta: C'est pourquoi nous mettons le "code qui marche" et éditons le code plus complexe dans un script. (13:14:38) davin.baragiotta: C'est ce que nous ferons après. Explorons d'abord dans l'interpréteur. (13:14:44) davin.baragiotta: PYTHON : QUELQUES BASES (13:14:49) davin.baragiotta: Toute programmation repose sur des conventions syntaxiques... En Python, on a de la chance, la syntaxe est très simple et intuitive. (13:14:56) davin.baragiotta: Les commentaires se font avec # : # ceci est un commentaire (13:15:01) davin.baragiotta: Les variables se déclarent sans $ devant ou autre fioriture. (13:15:06) davin.baragiotta: Les instructions ne se terminent pas avec des ; (13:15:11) davin.baragiotta: Les blocs de code ne sont pas structurés avec des { } mais par l'indentation. (13:15:18) davin.baragiotta: Les conventions pour le style de code en Python sont définies dans la PEP 8 (genre de norme) : http://www.python.org/dev/peps/pep-0008/ (13:15:25) davin.baragiotta: PYTHON : QUELQUES BASES : LES TYPES (13:15:33) davin.baragiotta: Voici les principaux types de base fournis avec le langage : n = None # NoneType : type spécial voulant dire... rien b = True # bool : booléen... True ou False n'oubliez pas les majuscules i = 15 # int : entier f = 15.5 # float : décimal s = "chaine" # str : chaine de caractère, instancié avec "" ou '' u = u"chaîne" # unicode : chaîne de caractère unicode, instancié avec u"" ou u'' l = [] # list : liste d'objets t = () # tuple : liste immuable d'objets d = {} # dict : dictionnaire de données (13:16:02) davin.baragiotta: PYTHON : QUELQUES BASES : OBJETS ET NAMESPACES (13:16:08) davin.baragiotta: En Python, tout est un "objet". Concrètement, ça veut dire que chaque "chose" qu'on manipule a: * un nom * des attributs (variables) accessibles "derrière" * des méthodes (fonctions) accessibles "derrière" (13:16:20) davin.baragiotta: Les attributs et méthodes ont des noms aussi. (13:16:26) davin.baragiotta: Mais ils se trouvent dans l' "espace de nom" de l'objet. (13:16:31) davin.baragiotta: Ils sont accessibles en utilisant un point pour séparer les noms : objet.attribut objet.methode() (13:16:40) davin.baragiotta: On peut chaîner les noms avec les points : objet.attribut.methode_attribut() (13:16:47) davin.baragiotta: Assez la théorie. Codons. (13:16:49) davin.baragiotta: :D (13:16:56) davin.baragiotta: INTROSPECTION (13:17:02) davin.baragiotta: Dans votre interpréteur déclarez une chaîne de caractères unicode : prenom = u"Davin" (13:17:13) davin.baragiotta: L'idée de l'introspection est de : interroger l'objet pour voir ce qu'on peut faire avec (13:17:21) davin.baragiotta: ipython nous offre une façon simple de regarder ça (13:17:28) davin.baragiotta: après le nom de votre variable... tapez '.' puis 'tab' prenom. [+ tab] (13:17:39) davin.baragiotta: vous voyez tous les noms (attributs et méthodes) accessibles sur cet objet unicode (13:17:50) davin.baragiotta: ('enter' pour en voir plus, 'q' pour sortir de la liste) (13:17:55) davin.baragiotta: tous les noms qui commencent avec __* sont des noms systèmes. (13:18:02) davin.baragiotta: Le nom seul n'est parfois pas suffisant pour savoir quoi faire avec... (13:18:10) davin.baragiotta: on va interroger l'interpréteur pour qu'il nous donne de l'info sur le nom (13:18:16) davin.baragiotta: derrière n'importe quel nom, on peut mettre '?' et taper 'enter' prenom.upper? (13:18:23) davin.baragiotta: ooooouuah! la partie docstring est carrément la documentation de la méthode! (13:18:25) davin.baragiotta: :D (13:18:34) davin.baragiotta: si on a trouvé la méthode qu'on voulait, on peut la tester : prenom.upper() (13:18:42) davin.baragiotta: note : l'objet prenom n'en est pas modifié... pour preuve, tapez : prenom (13:18:53) davin.baragiotta: Les fonctions built-in Python suivantes servent aussi à l'introspection : type(objet) # retourne le type de l'objet dir(objet) # retourne les noms derrière l'objet help(objet) # retourne l'aide callable(objet) # dit si un objet est appelable, exécutable... isinstance(objet, Type) # teste le classe (ou type) d'un objet ... (13:19:07) davin.baragiotta: Des questions? (13:19:27) olivier.larcheveque: Non tu peux continuer (13:19:38) davin.baragiotta: okay! (13:19:42) davin.baragiotta: PYTHON : QUELQUES BASES : APPELER UNE MÉTHODE (13:19:55) davin.baragiotta: Jouons un peu dans l'interpréteur avant de regarder un script nom = u"Baragiotta" # on créer une autre variable unicode l = [] # on crée une liste vide l. + tab # on introspecte la liste vide... (13:20:05) davin.baragiotta: on utiliserait quoi pour ajouter notre nom et prenom à la liste vide? (13:20:16) davin.baragiotta: l.append? (13:20:31) davin.baragiotta: l.append(prenom) l.append(nom) (13:20:38) davin.baragiotta: tapez : l (13:20:51) davin.baragiotta: Questions? (13:21:03) olivier.larcheveque: toujours pas c'est bon (13:21:05) davin.baragiotta: PYTHON : QUELQUES BASES : ITÉRATION (13:21:11) davin.baragiotta: Si vous voulez itérer, une structure de langage fort simple : for (13:21:31) davin.baragiotta: Exemple : for item in l: print item.upper() (13:21:42) davin.baragiotta: remarquez l'indentation de 4 espaces : pas de { } autour des instructions. (13:21:50) davin.baragiotta: pas de tabs dans vos scripts svp, on indente avec esapces : voir PEP 8 ;) (13:22:07) davin.baragiotta: Des questions? (13:22:12) olivier.larcheveque: QUESTION: PKOI mettre u"string", a quoi sert le 'u' (13:22:28) davin.baragiotta: il existe 3 types de chaines de caractères en Python (13:22:34) davin.baragiotta: string, unicode et raw (13:22:50) davin.baragiotta: elles se déclarent différemment avec : '' u'' r'' respectivement (13:23:06) davin.baragiotta: u'' = chaîne unicode, permet donc les caractères accentués (13:23:12) davin.baragiotta: sinon, '' = ascii (13:23:17) davin.baragiotta: on continue? (13:23:24) olivier.larcheveque: oui (13:23:28) davin.baragiotta: SCRIPTS (13:23:33) davin.baragiotta: Comme on le disait, c'est pas top coder dans l'interpréteur. Alors on va faire un script. (13:23:41) davin.baragiotta: il s'agit d'inclure du code dans un fichier et demander à l'interpréteur d'exécuter le code (13:23:47) davin.baragiotta: ouvrez dans un éditeur le fichier script.py : code/script.py (13:23:58) davin.baragiotta: la première ligne s'appelle shebang (13:24:02) davin.baragiotta: c'est pour dire au système que ce qui suit est du python (13:24:08) davin.baragiotta: ligne 2 : dit que c'est encodé en UTF-8 : ça nous permet d'écrire avec caractères accentués (notamment texte et commentaires) (13:24:21) davin.baragiotta: ligne 7 : un peu obscure, cette ligne se lit comme ça : si le nom de ce script est __main__ (ce qui est son nom par défaut quand lancé dans un interpréteur) alors exécute ce qui suit (13:24:38) davin.baragiotta: c'est là qu'on met les instructions du script pour les cas où on le lance avec un interpréteur (13:24:45) davin.baragiotta: lançons le script avec ipython : run script ou run script.py (13:24:56) davin.baragiotta: avec l'interpréteur normal ce serait juste : python script.py (13:25:04) davin.baragiotta: Questions? (13:25:14) olivier.larcheveque: non (13:25:15) davin.baragiotta: PYTHON : QUELQUES BASES : FONCTIONS (13:25:23) progfou: si tu me permets un détour (rapide) sur les accents (13:25:23) davin.baragiotta: Les fonctions sont du code qui reçoivent des paramètres.... et retournent un résultat (13:25:31) davin.baragiotta: progfou : je t'en prie (13:25:54) progfou: juste pour dire que la différence entre '' (str) et u'' (unicode) n'est pas le fait de pouvoir gérer les accents (13:26:20) progfou: la différence est que '' (str) va gérer des octets, alors que u'' (unicode) va gérer des caractères (13:26:25) progfou: un exemple pour finir : (13:27:16) progfou: si vous faites 'andré'.upper(), ça va vous donner n'importe quoi, car l'octet 'é' ne sera pas considéré comme le caractère 'é' mais comme une suite de 2 octets UTF-8 (13:27:30) progfou: tandis que u'andré'.upper() donnera bien u'ANDRÉ' (13:27:30) progfou: fini (13:27:43) davin.baragiotta: merci progfou pour ces précisions (13:27:50) davin.baragiotta: nous continuons avec les fonctions (13:27:57) davin.baragiotta: Une fonction est définie ici à la ligne 4 de script.py * def : pour déclarer une fonction ou une méthode (une méthode étant une fonction déclaré dans une classe) * les instructions de la fonction sont indentées : une indentation de 4 espaces * en fait ce qui est remarquable ici = pas d'accolades, pas de ; (13:28:18) davin.baragiotta: Voilà pour la partie théorique... il resterait beaucoup à dire (13:28:23) davin.baragiotta: mais les fonctionnalités Python sont faciles à explorer (13:28:30) davin.baragiotta: je vous laisse donc explorer par vous même : * avec la doc officielle * interactivement dans ipython (13:28:40) davin.baragiotta: C'est la semaine tech, jouons maintenant! ;) (13:28:48) davin.baragiotta: ### EXERCICE : solution pour le script intermédiaire : http://paste.pocoo.org/show/467931/ (13:29:19) davin.baragiotta: okay! (13:29:20) davin.baragiotta: MODULES : BOT JABBER : DEMO (13:29:27) davin.baragiotta: l'idée générale = créer un robot qui puisse être dans un salon jabber et interagir avec les users connectés (13:29:35) davin.baragiotta: ouvrez et éditez le fichier bot/conf.py.edit et sauvegardez le dans : bot/conf.py (13:29:51) davin.baragiotta: assurez-vous que : CHATROOM = "test@reunion.auf.org" (13:29:58) davin.baragiotta: si vous n'avez pas de username et de password pour votre bot : https://register.jabber.org/ (13:30:09) davin.baragiotta: tout le monde a créé un conf.py dans le répertoire bot? (13:30:46) davin.baragiotta: connectez vous (votre user normal humain, ex.: davin.baragiotta) au salon test de reunion.auf.org (comme vous avez fait pour vous connecter à ce salon-ci ;) ) (13:30:58) davin.baragiotta: je vais lancer mon bot qui se nomme 'barabotta', il va rejoindre le salon test (13:31:28) davin.baragiotta: je vous recommande de lancer votre bot avec l'interpréteur python normal : cd code/ python bot/bot.py (13:31:34) davin.baragiotta: sinon avec ipython, c'est : run bot/bot.py (13:31:40) davin.baragiotta: je vais montrer des commandes du bot et on regardera le code qui l'exécute avant que vous le modifiez (13:33:39) davin.baragiotta: on a regardé quelques commandes du bot (13:33:45) davin.baragiotta: regardons les sources (13:33:52) davin.baragiotta: MODULES : BOT JABBER : SOURCES (13:33:59) davin.baragiotta: ouvrez dans un éditeur le fichier bot/bot.py (13:34:07) davin.baragiotta: en haut du fichier, on connait maintenant : shebang et encoding (13:34:12) davin.baragiotta: lignes 4 à 8 (13:34:16) davin.baragiotta: mesdames et messieurs... (13:34:21) davin.baragiotta: c'est toute la puissance de Python devant vous (13:34:25) davin.baragiotta: les import !!!! (13:34:30) davin.baragiotta: :D (13:34:35) davin.baragiotta: PYTHON : QUELQUES BASES : IMPORTS (13:34:45) davin.baragiotta: le but du jeu, quand on code... c'est de ne pas réinventer la roue (13:34:51) davin.baragiotta: pour ce faire, on importe le code qui règle déjà nos problèmes... (13:35:05) davin.baragiotta: ligne 4 from datetime import datetime (13:35:10) davin.baragiotta: ça se lit comme suit : du module (standard) datetime on veut importer le nom datetime (13:35:15) davin.baragiotta: en gros, rend accessible dans le code : datetime.datetime (13:35:23) davin.baragiotta: vous voulez en savoir plus sur ce module? voir la doc officielle : http://docs.python.org/library/ (13:35:33) davin.baragiotta: cherchez datetime : http://docs.python.org/library/datetime.html (13:35:39) davin.baragiotta: lisez ;) (13:35:46) davin.baragiotta: sinon, importez le module dans interpréteur et introspectez!!! : import datetime datetime. [+ tab] datetime.datetime? [+ tab] (13:36:04) davin.baragiotta: continuons (13:36:12) davin.baragiotta: import re = import du module d'expressions régulières (13:36:18) davin.baragiotta: en fait... tous les messages échangés via jabber nous arrivent comme des lignes de texte.... (13:36:24) davin.baragiotta: on veut pouvoir chercher des mots dans les lignes pour trouver les commandes données au bot! (13:36:33) davin.baragiotta: re = regex = regular expression http://docs.python.org/library/re.html (13:36:41) davin.baragiotta: (regex = un petit monde en soit au niveau de la syntaxe) (13:36:51) davin.baragiotta: on continue (13:36:57) davin.baragiotta: après mes imports de la librairie standard (13:37:01) davin.baragiotta: j'importe les modules particuliers installés sur mon système... (13:37:16) davin.baragiotta: jabberbot est accessible car vous l'avez installé via un paquet Debian avant l'atelier (13:37:17) davin.baragiotta: il est accessible par son nom de module python (13:37:21) davin.baragiotta: il y a 2 façons d'importer : from ... import ... (13:37:37) davin.baragiotta: PYTHON : QUELQUES BASES : IMPORTS : IMPORT (13:37:57) davin.baragiotta: import re , par exemple, charge tout le module re (13:38:06) davin.baragiotta: les objets du modules sont accessibles dans le code en les préfixant par le nom du module (13:38:12) davin.baragiotta: ex.: ligne 38 j'utilise compile de re en codant : re.compile (13:38:28) davin.baragiotta: PYTHON : QUELQUES BASES : IMPORTS : FROM (13:38:33) davin.baragiotta: from charge seulement des noms spécifiques... accessibles directement, sans préfixer (13:38:42) davin.baragiotta: ex.: ligne 10 j'utilise MUCJabberBot de muc en codant : MUCJabberBot (n'est pas préfixée par muc : muc.MUCJabberBot) (13:38:55) davin.baragiotta: PYTHON : QUELQUES BASES : MODULES (13:39:00) davin.baragiotta: ligne 8 : j'importe MUCJabberBot de muc... mais on n'a pas installé muc sur notre système... (13:39:07) davin.baragiotta: ... mais il est accessible dans le contexte (puisqu'on l'importe) : muc vient d'où? (13:39:15) davin.baragiotta: il vient de *notre* module bot (13:39:21) davin.baragiotta: c'est que le répertoire bot est en soi un module python (13:39:31) davin.baragiotta: comment créer un module? (13:39:39) davin.baragiotta: tout simplement en ajoutant un fichier (vide) nommé __init__.py dans un répertoire (13:39:49) davin.baragiotta: grâce à bot/__init__.py, on peut importer du contenu d'un fichier .py (de ce répertoire) dans un autre fichier .py (13:39:56) davin.baragiotta: preuve : bot.py import à la ligne 8 MUCJabberBot en provenant de muc.py (13:40:09) davin.baragiotta: Des questions? (13:40:41) olivier.larcheveque: QUESTION: quel lien pour tester notre compte de jabber? (J'ai su oublié mon mot de passe) (13:40:57) davin.baragiotta: https://register.jabber.org/ (13:41:04) davin.baragiotta: je crois on peut modifier mdp ;) (13:41:12) davin.baragiotta: pas sûr (13:41:17) olivier.larcheveque: ok pas d'autres questions autrement (13:41:25) davin.baragiotta: Codons, codons, codons (13:41:27) davin.baragiotta: MODULES : BOT JABBER : PERSONNALISER SON BOT PAR UNE SIMPLE MODIF (13:41:34) davin.baragiotta: Pour ceux qui ont un bot qui marche, on va le personnaliser (13:41:41) davin.baragiotta: il y a 2 approches pour l'interaction du bot : * une commande * une recherche de mot-clé par regex (13:41:48) davin.baragiotta: on va commencer par personnaliser en mettant un mot dans regex (13:41:56) davin.baragiotta: copiez la ligne 38 et collez la dessous, en ligne 39 (13:42:04) davin.baragiotta: modifiez la ligne 39 : * le nom de l'attribut de self : ex.: par self.re_hourra * la chaîne de caractère dans le regex : ex.: par u'.*(?Phourra).*' (13:42:17) davin.baragiotta: exemple : self.re_biere = re.compile(u'.*(?Pbi[eè]re).*') self.re_hourra = re.compile(u'.*(?Phourra).*') (13:42:25) davin.baragiotta: avec ça, on le patron regex pour trouver le mot hourra dans une chaine de caractères (13:42:44) davin.baragiotta: ensuite (13:42:47) davin.baragiotta: ligne 50 et 51 : copiez et collez dessous (13:42:52) davin.baragiotta: modifiez les lignes 52 et 53 : * en remplaçant 'biere' par 'hourra' (13:42:59) davin.baragiotta: exemple : if self.re_biere.match(message): self.biere(mess) if self.re_hourra.match(message): self.hourra(mess) (13:43:09) davin.baragiotta: avec ça on vient de dire : si le message matche le pattern (regex) écrit dans self.re_hourra alors on exécute la méthode (fonction) hourra sur self (le bot lui-même)... (13:43:21) davin.baragiotta: mais on n'a pas encore déclaré la méthode self.hourra() : créons la (13:43:32) davin.baragiotta: copiez def biere au grand complet (lignes 84 à 89)... ... et collez le code dessous (13:43:42) davin.baragiotta: on modifie : * le nom de la méthode définie * la réponse retournée par cette méthode du bot (13:43:51) davin.baragiotta: exemple : def biere(self, mess): """Aux messages relatifs à la bière, choisit une de ses réponses prédéfinies""" username = self.get_sender_username(mess) if username != self.username and username != self.nickname: response = 'St-Ambroise!' self.send_simple_reply(mess, response) def hourra(self, mess): """Aux messages relatifs à la bière, choisit une de ses réponses prédéfinies""" username = self.get_sender_username(mess) if username != self.username and username != self.nickname: response = 'Non... je ne suis pas de bonne humeur.' self.send_simple_reply(mess, response) (13:44:07) davin.baragiotta: on vient de rendre sensible notre bot au mot 'hourra' où qu'il soit dans un message... ... afin qu'il réponde : 'Non... je ne suis pas de bonne humeur.' (13:44:22) davin.baragiotta: Pour tester le nouveau comportement du bot : * fermer dans votre terminal le bot en cours : Ctrl + C * si il traine dans le salon trop longtemps encore, dans le salon : /kick monbot * relancer le bot : python bot/bot.py (13:46:16) davin.baragiotta: on est dans le salon test à tester nos bots modifiés (13:47:20) davin.baragiotta: ok on continue (13:47:30) davin.baragiotta: MODULES : BOT JABBER : PIMPER SON BOT PAR L'AJOUT DE COMMANDE (13:47:39) davin.baragiotta: ### EXERCICE : solution pour la commande finale du bot : http://paste.pocoo.org/show/467932/ (13:48:09) davin.baragiotta: L'autre approche pour l'interaction est la commande. (13:48:15) davin.baragiotta: Les commandes sont des méthodes "décorées" par le décorateur : @botcmd (13:48:23) davin.baragiotta: Un décorateur est une fonction qui s'exécute avant une fonction. (13:48:30) davin.baragiotta: Pour utiliser une commande, tapez simplement son nom dans le salon, suivi des paramètres qu'elle accepte. (13:48:44) davin.baragiotta: Exemple, comme vu dans la démo avec : redmine #123 (13:48:52) davin.baragiotta: Pour que seul votre bot réponde, vous pouvez préfixer le tout de son nom : barabotta redmine #123 (13:49:00) davin.baragiotta: Pour créer votre propre commande... * copiez la commande sieste : lignes 61 à 64... n'oubliez pas le décorateur * collez le code dessous * modifiez le nom de la commande * modifiez le contenu de la réponse : variable 'reply' (13:49:17) davin.baragiotta: Tester la commande dans le salon * fermer dans votre terminal le bot en cours : Ctrl + C * si il traine dans le salon trop longtemps encore, dans le salon : /kick monbot * relancer le bot : python bot/bot.py (13:49:35) davin.baragiotta: Des questions? (13:49:47) olivier.larcheveque: non (13:50:41) davin.baragiotta: ok, j'ai terminé (13:50:57) davin.baragiotta: on pourrait parler des boucles sans fin de lundi (13:51:11) davin.baragiotta: barabotta est encore "armé" pour faire une boucle sans fin ;) (13:51:37) davin.baragiotta: mais il manque un peu d'activité dans le salon test : c'est plein d'humains et peu de bot ;) (14:00:14) davin.baragiotta: maitenant, c'est plein de vie (14:00:16) davin.baragiotta: :D (14:00:32) davin.baragiotta: ---------- FIN : Atelier : Programmation Python ---------------- }}}