Valid XHTML     Valid CSS2    

DECRA, T.P. 2 :

     Programmation en Python

Décomposition, Conception et Réalisation d'Applications

                    gilles.hunault "at" univ-angers.fr

 

Table des matières cliquable

  1. Vérification du tuteur Python

  2. Réindentation de code Python

  3. Compréhension de code Python

  4. Modes de développement et d'exécution en Python

  5. Archivage incrémental

  6. Utilisation d'un Jupyter notebook pour Python

  7. Scripts Python avec un argument en ligne de commande

Il est possible d'afficher toutes les solutions via ?solutions=1 et de les masquer via ?solutions=0.

Remarque : sauf pour les exercices 3 et 6, les exercices sont à réaliser dans un terminal, à l'aide de l'interpréteur indiqué, à savoir python3 ou ipython3.

Utiliser un répertoire pour tous vos développements de ce T.P., par exemple nommé tp2/ ou DCRA-tp2/ est une pratique conseillée. Dans les salles du département informatique, le chemin absolu pour les notebooks, quant à lui, est /media/D/Mes_notebooks/.

 

1. Vérification du tuteur Python

Lire le tuteur Python et essayer de vérifier que chaque code Python fourni est correct à l'aide la commande python3 script.pyscript.py est le nom du fichier qui contient le code.

Lorsque le nom du fichier python est donné, il faut le télécharger à l'aide de la commande wget car sinon, par copier/coller, on risque de perdre l'indentation qui est très stricte en Python. On supprimera le code # -*- coding:latin1 -*- si un fichier commence par une telle entête et on adaptera le code en conséquence.

On commencera par vérifier la version de Python utilisée.

Remarque : on pourra utiliser geany comme éditeur de code Python.

Quelques questions à propos des exemples du tuteur :

  • Est-ce que les exemples bonjour.py, if2.py , if3.py , python_demo5.py et python_demo6.py fonctionnent tels quels dans les salles informatiques de l'université ou sur votre ordinateur (si vous êtes en distanciel) ? Si non, que faut-il installer et comment, sachant qu'on n'a pas le droit d'être administrateur (root) sur ces ordinateurs ?

  • Est-ce que les exemples tdmv1.py et tdmv2.py (qui utilisent easygui.py) fonctionnent tels quels dans les salles informatiques de l'université ou sur votre ordinateur ? Si non, que faut-il installer et comment, sachant qu'on n'a pas le droit d'être administrateur (root) sur ces ordinateurs ?

  • Est-il possible de tester l'exemple de la section 4 qui utilise le module pymysql ? et le script getndjpm.py ? et les autres scripts de cette section 4 ?

  • Est-ce que les exemples qui utilisent XML fonctionnent dans les salles du département informatique de l'université d'Angers ou sur votre ordinateur ?

Solution :  

 

2. Réindentation de code Python

Python est un des rares langages à imposer une indentation stricte pour les blocs. Ainsi le programme malindent.py ci-dessous n'est pas exécutable. Réindenter son code pour qu'il s'exécute correctement. On devra obtenir comme résultat d'exécution du fichier correct le fichier bienindent_res.txt.

Au passage, que réalise ce script Python ?


     # mini mvc en python3
     
     class Model:
     def get_post(self):
     return {"title":"A test","body":"An example.."}
     
     class View:
     def display(self,items):
     print('Title:' + items['title'] + '\n'+'Body:' + items['body'])
     
     class Controller:
     def __init__(self):
     self.model=Model()
     self.view=View()
     
     def main(self):
     post=self.model.get_post()
     self.view.display(post)
     
     mvc=Controller()
     mvc.main()
     

Réindenter aussi mini_mvc2_py.txt et mini_mvc3_py.txt avant de vérifier que ces programmes s'exécutent en Python 2. Les convertir ensuite en Python 3. Que peut-on en déduire sur MVC ?


     # mini_mvc2_py.txt
     
     class View():
     def user(self,users):
     print(users.find('two'))
     
     class Control:
     def find(self,user):
     return self._look(user)
     
     def _look(self,user):
     if user in self.users:
     return self.users[user]
     else:
     return 'The data class ({}) has no {}'.format(self.userName(),user)
     
     def userName(self):
     return self.__class__.__name__.lower()
     
     class Model(Control):
     users=dict(one='Bob',two='Michael',three='Dave')
     
     def main():
     users=Model()
     find=View()
     print('--> The user two\'s "real name" is:\n')
     find.user(users)
     
     if __name__=="__main__":
     main()
     

     # mini_mvc3_py.txt
     
     # Source : http://tkinter.unpythonic.net/wiki/ToyMVC
     
     ## Some points to mention...
     ##
     ## The model knows nothing about the view or the controller.
     ## The view knows nothing about the controller or the model.
     ## The controller understands both the model and the view.
     ##
     ## The model uses observables, essentially when important data is changed,
     ## any interested listener gets notified through a callback mechanism.
     ##
     ## The following opens up two windows, one that reports how much money you
     ## have, and one that has two buttons, one to add money and one to remove
     ## money.
     ##
     ## The important thing is that the controller is set up to monitor changes
     ## in the model.  In this case the controller notices that you clicked a
     ## button and modifies the money in the model which then sends out a
     ## message that it has changed.  The controller notices this and updates
     ## the widgets.
     ##
     ## The cool thing is that anything modifying the model will notify the
     ## controller.  In this case it is the controller modifying the model, but it
     ## could be anything else, even another controller off in the distance
     ## looking at something else.
     ##
     ## The main idea is that you give a controller the model and view that it
     ## needs, but the model's can be shared between controllers so that when
     ## the model is updated, all associated views are updated. -Brian Kelley
     ##
     ## following is a Tkinter approximation of the original example.
     
     import Tkinter as tk
     
     class Model:
         def __init__(self):
             self.myMoney = Observable(0)
     
         def addMoney(self, value):
             self.myMoney.set(self.myMoney.get() + value)
     
         def removeMoney(self, value):
             self.myMoney.set(self.myMoney.get() - value)
     
     class View(tk.Toplevel):
         def __init__(self, master):
             tk.Toplevel.__init__(self, master)
             self.protocol('WM_DELETE_WINDOW', self.master.destroy)
             tk.Label(self, text='My Money').pack(side='left')
             self.moneyCtrl = tk.Entry(self, width=8)
             self.moneyCtrl.pack(side='left')
     
         def SetMoney(self, money):
             self.moneyCtrl.delete(0,'end')
             self.moneyCtrl.insert('end', str(money))
     
     class Controller:
         def __init__(self, root):
             self.model = Model()
             self.model.myMoney.addCallback(self.MoneyChanged)
             self.view1 = View(root)
             self.view2 = ChangerWidget(self.view1)
             self.view2.addButton.config(command=self.AddMoney)
             self.view2.removeButton.config(command=self.RemoveMoney)
             self.MoneyChanged(self.model.myMoney.get())
     
         def AddMoney(self):
             self.model.addMoney(10)
     
         def RemoveMoney(self):
             self.model.removeMoney(10)
     
         def MoneyChanged(self, money):
             self.view1.SetMoney(money)
     
     class Observable:
         def __init__(self, initialValue=None):
             self.data = initialValue
             self.callbacks = {}
     
         def addCallback(self, func):
             self.callbacks[func] = 1
     
         def delCallback(self, func):
             del self.callback[func]
     
         def _docallbacks(self):
             for func in self.callbacks:
                  func(self.data)
     
         def set(self, data):
             self.data = data
             self._docallbacks()
     
         def get(self):
             return self.data
     
         def unset(self):
             self.data = None
     
     class ChangerWidget(tk.Toplevel):
         def __init__(self, master):
             tk.Toplevel.__init__(self, master)
             self.addButton = tk.Button(self, text='Add', width=8)
             self.addButton.pack(side='left')
             self.removeButton = tk.Button(self, text='Remove', width=8)
             self.removeButton.pack(side='left')
     
     if __name__ == '__main__':
     root = tk.Tk()
     root.withdraw()
     app = Controller(root)
     root.mainloop()
     

Solution :  

 

3. Compréhension de code Python

L'une des forces du langage Python est de permettre l'écriture de code en mode lambda-calcul.

Corriger le code obfus-1.py ci-dessous qui n'est pas exécutable à cause d'erreur(s) simple(s) de syntaxe. Qu'affiche-t-il alors ? Au passage, est-ce du Python 2 ou du Python 3 ? Si c'est du Python 2, donner la version Python 3 correspondante.


     print filter(None,map(lambda y:y*reduce(lambda x,y:x*y!=0,map(lambda x,y=y:y%x,range(2,int(pow(y;0,5)+1))),1),range(2,1000)))
     

Si le style one-liner vous perturbe, voici une version indentée, tout aussi absconse, à la limite de l'obfuscated :


     print filter(
           None,map(
              lambda y:y*reduce(
              lambda x,y:x*y!=0,map(
                lambda x,y=y:y%x,
                range(2,int(pow(y;0,5)+1))
              ),1), # fin de range
              range(2,1000)
           ) # fin de map
     ) # fin de filter
     

Que peut-on penser de cette façon de programmer ?

Solution :  

 

4. Modes de développement et d'exécution en Python

Y a-t-il une différence entre l'exécution via python script.py et l'exécution via ipython script.py pour les exemples du tuteur ?

Plus globalement, quel est l'intérêt d'utiliser ipython plutôt que python au niveau du développement ?

Serait-ce mieux de développer avec un Jupyter Notebook ?

Solution :  

 

5. Archivage incrémental

On voudrait qu'à l'exécution du script archcd.py il y ait une production automatique d'une archive au format tar pour tous les fichiers de type xml avec numérotation de l'archive. Par exemple la première fois que le script est exécuté, on produira l'archive lesxml001.tar ; la seconde fois, ce sera lesxml002.tar etc. L'algorithme de base ressemble certainement à


      # algorithme d'archivage en .tar des *.xml
     
         affecter numarchive <-- 1
         construire nomarchive à partir de numarchive
     
         tant_que le fichier nomarchive existe
             affecter numarchive <-- numarchive + 1
             construire nomarchive à partir de numarchive
         fin tant_que le fichier nomarchive existe
     
         exécuter la commande tar pour les fichiers indiqués
         afficher un message indiquant que l'archivage est fait
     

Voici la sortie des deux premières exécutions du script :


     $gh> python archcd.py
     [python] archivage des fichiers *.xml dans lesxml001.tar terminée
     
     $gh> python archcd.py
     [python] archivage des fichiers *.xml dans lesxml002.tar terminée
     

Solution :  

 

6. Utilisation d'un Jupyter notebook pour Python

Télécharger et compléter le Jupyter ipython notebook nommé colExcel.

Pour mémoire, dans les salles du département informatique, if faut, dans un terminal lancer la commande _jupyter_notebook c'est-à-dire jupyter_notebook précédé du souligné _ pour pouvoir utiliser un serveur local de notebooks. Le disque D et le répertoire Mes_notebooks permettent alors d'enregistrer les fichiers. Il est conseillé de télécharger le notebook colExcel dans le répertoire Mes_notebooks avant de lancer le serveur Jupyter.

Lorsqu'on exécute la commande _jupyter_notebook, le serveur jupyter est lancé et il faut le laisser actif pour pouvoir continuer. Voici un exemple de session dans le terminal 


     gilles.hunault@g105-0:~$ cd /media/D/Mes_notebooks/
     
     gilles.hunault@g105-0:~$ wget http://forge.info.univ-angers.fr/~gh/Notebooks/colExcel.ipynb
     
     gilles.hunault@g105-0:~$ _jupyter_notebook
     
     Executing the command: jupyter notebook
     
     [I 14:54:26.939 NotebookApp] Writing notebook server cookie secret to /home/jovyan/.local/share/jupyter/runtime/notebook_cookie_secret
     [W 14:54:28.321 NotebookApp] WARNING: The notebook server is listening on all IP addresses and not using encryption. This is not recommended.
     [I 14:54:28.557 NotebookApp] JupyterLab extension loaded from /opt/conda/lib/python3.6/site-packages/jupyterlab
     [I 14:54:28.557 NotebookApp] JupyterLab application directory is /opt/conda/share/jupyter/lab
     [I 14:54:28.674 NotebookApp] Serving notebooks from local directory: /home/notebook
     [I 14:54:28.675 NotebookApp] The Jupyter Notebook is running at:
     [I 14:54:28.675 NotebookApp] http://(a78bb68062a9 or 127.0.0.1):8888/?token=aab6d8f5aa8f82f3c21a8f16661e556b0642cde765e3883e
     [I 14:54:28.675 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
     [C 14:54:28.675 NotebookApp]
     
         Copy/paste this URL into your browser when you connect for the first time,
         to login with a token:
             http://(a78bb68062a9 or 127.0.0.1):8888/?token=aab6d8f5aa8f82f3c21a8f16661e556b0642cde765e3883e
     

Pour accéder au serveur, il faut utiliser via un navigateur Web le port indiqué et le jeton (token) indiqué. Ce n'est malheureusement pas simple car ce qui est affiché dans le terminal n'est pas prévu pour un copier/coller. Pour l'exemple précédent, l'URL de connection est :


             http://127.0.0.1:8888/?token=aab6d8f5aa8f82f3c21a8f16661e556b0642cde765e3883e
     

Si vous n'avez tapé que la partie http://127.0.0.1:8888 il est possible de saisir ensuite le token, sans oublier les caractères /?token=.

Voici ce que vous devriez voir si tout se passe bien :

               non su

Pour terminer proprement la session avec jupyter, il faut arrêter le serveur avec deux fois Controle-C puis fermer le terminal.

Pour mémoire, le chemin absolu pour les notebooks est /media/D/Mes_notebooks/.

Solution :  

 

7. Scripts Python avec un argument en ligne de commande

Ecrire un script Python nommé colExcel.py qui fonctionne en ligne de commandes et qui correspond aux spécifications suivantes :

  1. sans paramètre, le script fournit un rappel de la syntaxe et des exemples.

  2. le script importe le fichier colExcelInc.py qui contient deux fonctions, à savoir fromExcel() et toExcel() présentées dans l'exercice précédent.

  3. le fichier à importer colExcelInc.py contient un auto-test permettant de vérifier que les deux fonctions sont correctes.

  4. le script colExcel.py admet un seul paramètre nommé inpCol.

  5. le script colExcel.py détecte la nature du seul parametre nommé inpCol ; si c'est un nombre entier strictement positif, on éxécute toExcel() ; si c'est une chaine de caractères comportant un seul mot ne comportant que des majuscules (non accentuées), on éxécute fromExcel() ; dans les autres cas, on dit qu'il y a une erreur.

Voici le comportement attendu :


     $gh> python3 colExcel.py
     
        colExcel.py ; conversions de numéros et noms de colonnes comme Excel
     
        syntaxe  : python3 colExcel.py NOMBRE | MOT
        exemples : python3 colExcel.py 218
                   python3 colExcel.py OUI
     
        Attention, NOMBRE doit être un entier strictement positif
        et MOT un seul mot en lettres majuscules non accentuées.
     
     $gh> python3 colExcelInc.py
     
     la colonne numéro      1 se nomme A   sous Excel
     la colonne numéro      2 se nomme B   sous Excel
     la colonne numéro     25 se nomme Y   sous Excel
     la colonne numéro     26 se nomme Z   sous Excel
     la colonne numéro     27 se nomme AA  sous Excel
     la colonne numéro     28 se nomme AB  sous Excel
     la colonne numéro     99 se nomme CU  sous Excel
     la colonne numéro    100 se nomme CV  sous Excel
     la colonne numéro   2018 se nomme BYP sous Excel
     
     la colonne nommée A   sous Excel est en fait la colonne numéro     1
     la colonne nommée B   sous Excel est en fait la colonne numéro     2
     la colonne nommée Z   sous Excel est en fait la colonne numéro    26
     la colonne nommée AA  sous Excel est en fait la colonne numéro    27
     la colonne nommée AZ  sous Excel est en fait la colonne numéro    52
     la colonne nommée BA  sous Excel est en fait la colonne numéro    53
     la colonne nommée BYE sous Excel est en fait la colonne numéro  2007
     la colonne nommée MCM sous Excel est en fait la colonne numéro  8879
     
     $gh> python3 colExcel.py 125 OUI
     
        colExcel.py ; conversions de numéros et noms de colonnes comme Excel
     
        syntaxe  : python3 colExcel.py NOMBRE | MOT
        exemples : python3 colExcel.py 218
                   python3 colExcel.py OUI
     
        Attention, NOMBRE doit être un entier strictement positif
        et MOT un seul mot en lettres majuscules non accentuées.
     
     
     $gh> python3 colExcel.py 125
     
     la colonne numéro    125 se nomme DU  sous Excel
     
     $gh> python3 colExcel.py OUI
     
     la colonne nommée OUI sous Excel est en fait la colonne numéro 10695
     
     $gh> python3 colExcel.py oui
     
     La valeur fournie, à savoir 'oui' n'est ni un entier strictement positif ni un seul mot en lettres majuscules non accentuées.
     
     $gh> python3 colExcel.py -3
     
     La valeur fournie, à savoir '-3' n'est ni un entier strictement positif ni un seul mot en lettres majuscules non accentuées.
     

Si vous n'avez pas réussi à résoudre l'exercice précédent ou à télécharger ses solutions, vous pouvez essayer de simplifier l'exercice en travaillant uniquement avec les nombres 1 à 26, c'est-à-dire avec les colonnes A à Z.

Ecrire ensuite un deuxième script Python nommé decritExcel.py qui fonctionne en ligne de commandes et qui correspond aux spécifications suivantes :

  • s'il n'y a pas de paramètre, on fournit un rappel de la syntaxe et un exemple puis on s'arrête.

  • si le fichier Excel désigné par le paramètre n'est pas présent, on l'indique et on s'arrête.

  • si le fichier est présent, on liste les colonnes présentes avec leur numéro et leur nom sous Excel (A pour 1, B pour 2...).

Pour les expert(e)s, on pourra de plus fournir le type des colonnes, la valeur minimale, la valeur maximale et le nombre de valeurs manquantes, comme ci-dessous, pour le fichier iris.xlsx :


     $gh> python3 decritExcel.py iris.xlsx
     
     Description du fichier iris.xlsx
     ================================
     
     150 lignes et 6 colonnes lues dans ce fichier.
     
     
             Numéro Colonne Excel NbVal   Min   Max Distinct Manquantes      Type
     iden         1             A   150  I001  I150      150          0 character
     espece       2             B   150 1.000 3.000        3          0   numeric
     longsep      3             C   150 4.300 7.900       35          0   numeric
     largsep      4             D   150 2.000 4.400       23          0   numeric
     longpet      5             E   150 1.000 6.900       43          0   numeric
     largpet      6             F   150 0.100 2.500       22          0   numeric
     

Solution :  

 

       retour au plan de cours  

 

Code-source PHP de cette page ; code Javascript associé.

 

retour gH    Retour à la page principale de   (gH)