Python pour PC* Daniel Lecouturier 2018 2019 - Orange
←
→
Transcription du contenu de la page
Si votre navigateur ne rend pas la page correctement, lisez s'il vous plaît le contenu de la page ci-dessous
Table des matières 1 Communiquer avec des fichiers 4 1.1 Chemin d’accès à un fichier ou un répertoire . . . . . . . . . . . . . . . . . . . 4 1.1.1 Nom . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 1.1.2 Choix du répertoire d’enregistrement . . . . . . . . . . . . . . . . . . . 4 1.1.3 slash, antislash . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 1.2 Ouverture de fichier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 1.2.1 En mode lecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 1.2.2 En mode écriture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 1.3 Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 1.3.1 Différents modes d’importation . . . . . . . . . . . . . . . . . . . . . . 6 1.3.2 Modules sys et os . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 1.3.3 Enregistrer ses propres modules . . . . . . . . . . . . . . . . . . . . . . 6 2 Ingéniérie numérique 8 2.1 Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 2.1.1 Présentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 2.1.2 Alias d’importation usuels . . . . . . . . . . . . . . . . . . . . . . . . . 9 2.2 numpy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 2.2.1 Tableaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 2.2.2 Vectorisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 2.2.3 Constructeurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 2.2.4 Vue et copie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 2.2.5 Sauvegarde . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 2.3 matplotlib . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 2.3.1 Syntaxe minimale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 2.3.2 Lignes de niveau . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 2.3.3 Syntaxe enrichie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 2.3.4 Images . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 2.3.5 3d . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 2.4 scipy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 2.4.1 Résoudre des équations . . . . . . . . . . . . . . . . . . . . . . . . . . 16 2.4.2 Intégrer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 2.4.3 Résoudre une ODE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 3 Représentation binaire de données 19 3.1 Entiers en base 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 3.2 Entiers relatifs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 3.3 Flottants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 3.4 Caractères . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 3.5 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 1
4 Fonctions 26 4.1 Généralités . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 4.1.1 En tête . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 4.1.2 Valeur de retour . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 4.2 Portée des variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 4.2.1 Nom de variable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 4.2.2 Variables globales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 4.2.3 Liaison dynamique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 4.2.4 Variables locales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 4.2.5 Fonctions locales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 4.3 Passage des arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 4.3.1 Passage par valeurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 4.3.2 Fonction qui modifie ses arguments . . . . . . . . . . . . . . . . . . . . 31 4.3.3 Attention aux raccourcis . . . . . . . . . . . . . . . . . . . . . . . . . . 32 4.3.4 Valeurs par défaut . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 4.4 Méthodes pour objets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 5 La structure de Pile 34 5.1 Structures de données . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 5.2 La structure de pile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 5.3 Implémentation minimaliste . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 5.4 Implémentation par liste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 5.5 Implémentation par tableau . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 5.6 Implémentation par objet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 5.6.1 Par héritage de la structure de liste . . . . . . . . . . . . . . . . . . . . 38 5.6.2 Par chaı̂nage d’objets . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 6 Récursivité 41 6.1 Fonction récursive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 6.1.1 Une fonction peut en appeler une autre . . . . . . . . . . . . . . . . . 41 6.1.2 Une fonction récursive s’appelle elle-même . . . . . . . . . . . . . . . 41 6.2 Appels récursifs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 6.3 Limitations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 6.4 Caractéristiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 6.5 Code itératif ou récursif ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 7 Algorithmes de tri 44 7.1 Ressources en ligne . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 7.2 Tri par dénombrement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 7.3 Tri par sélection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 7.4 Tri par insertion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 7.5 Tri par fusion (alias merge sort) . . . . . . . . . . . . . . . . . . . . . . . . . . 48 7.6 Tri rapide (alias quicksort) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 8 Bases de données 53 8.1 Définitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 8.2 Langage SQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 8.3 Interface graphique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 8.4 Dialogue avec une base de données . . . . . . . . . . . . . . . . . . . . . . . . 54 8.4.1 Casse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 8.4.2 Administration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 2
8.4.3 Opérations ensemblistes . . . . . . . . . . . . . . . . . . . . . . . . . . 55 8.4.4 Projection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 8.4.5 Sélection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 8.4.6 Jointure symétrique . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 8.4.7 Alias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 8.4.8 Agrégation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 8.4.9 Résumé . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 8.5 Base-jouet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 8.6 Vue sur les menus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 3
Chapitre 1 Communiquer avec des fichiers 1.1 Chemin d’accès à un fichier ou un répertoire 1.1.1 Nom Le nom seul du fichier dans une session PYTHON fait implicitement référence à un fichier du répertoire courant. Pour connaı̂tre ce répertoire, taper cd dans le shell ou dans un script (pas de print pour une fois...). import matplotlib.pyplot as plt import numpy as np from mpl_toolkits.mplot3d import Axes3D plt.close() #au cas où une figure serait active ax = Axes3D(plt.figure()) X = np.linspace(-5, 5, 100) Y = np.linspace(-1.5, 1.5, 100) X, Y = np.meshgrid(X, Y) Z = -np.cos(X)+Y**2/2 ax.plot_wireframe(X, Y, Z, rstride=10, cstride=10) plt.show() plt.savefig(’fildefer.png’) cd L’image est enregistrée sous le nom fildefer.png (l’extension .png précise l’encodage de l’image) dans le répertoire indiqué en réponse à la commande cd. 1.1.2 Choix du répertoire d’enregistrement Il est possible de changer ce comportement pour enregistrer à un endroit choisi. • en indiquant le chemin complet, depuis la racine. plt.savefig(’C:/Users/daniel/python/graphiques/fildefer.png’) • en indiquant le chemin relatif, du répertoire courant à un sous-répertoire (c’est plus restrictif que la méthode précédente). #en supposant que C:/Users/daniel est le repertoire courant et que Python/graphiques est un sous-repertoire plt.savefig(’Python/graphiques/fildefer.png’) 4
• en changeant le répertoire courant avec le module os. On peut encore désigner l’empla- cement de ce nouveau répertoire par son chemin complet (depuis la racine), ou si c’est un sous-répertoire du répertoire courant, par le chemin relatif. import os print(os.getcwd()) #noter le print! os.chdir(’Python/graphiques’) #chemin relatif print(os.getcwd()) #verification plt.savefig(’fildefer.png’) 1.1.3 slash, antislash Le séparateur \ (antislash) utilisé par windows est un caractère spécial qui change l’in- terprétation du caractère qui le suit. Par exemple \n désigne une fin de ligne. Il est donc vivement recommandé de le remplacer par un slash / ou de le doubler ainsi \\ Python essaye de doubler les antislash, avec un bonheur inégal. Il vaut mieux s’en charger soi-même. C’est la solution slash qui a été adoptée dans les exemples précédents. 1.2 Ouverture de fichier 1.2.1 En mode lecture #pour enregistrer la lecture de tout le fichier dans une chaine with open(’chemin’,’r’) as f: t = f.read() #pour enregistrer la lecture de la premiere ligne dans une chaine with open(’chemin’,’r’) as f: t = f.readline() #pour enregistrer la lecture de toutes les lignes dans une liste de chaines with open(’chemin’,’r’) as f: t = f.readlines() • Avec un argument dans read(), on contrôle le nombre de caractères lus. • Noter que la lecture est séquentielle : si vous venez d’exécuter, juste après l’ouver- ture, f.read(50), la lecture se poursuivra à partir du caractère suivant. Par exemple, f.readline() lira du cinquante-et-unième caracère jusqu’à la prochaine fin de ligne. • Si le fichier n’existe pas (à l’emplacement indiqué), PYTHON affiche un message d’erreur. 1.2.2 En mode écriture with open(’chemin’,’w’) as f: f.write(chaine) Il vaut mieux donner un nom de fichier qui n’existe pas encore (sinon, le contenu est pro- gressivement effacé et remplacé par les caractères écrits). Là aussi, l’écriture est séquentielle et reprend où elle s’est arrêtée (jusquà la fermeture du fichier, qui intervient quand le code est aligné avec with, à la fin de l’indentation). 5
1.3 Modules 1.3.1 Différents modes d’importation Prenons l’exemple du module math. On ne le répétera jamais assez : l’instruction dir(math) donne la liste des fonctions qu’il renferme, l’instruction help(math) documente chacune de ces fonctions. Table 1.1 – Différents modes d’importation d’un module Mode d’importation Appel de commande import math math.cos(2) import math as m m.cos(2) from math import cos cos(2) from math import * tan(2) IMPORTANT : Les deux premières méthodes évitent les conflits de noms (par exemple, de nombreux modules contiennent une fonction cos !) : c’est l’intérêt même de la notation ob- jet. Il est donc conseillé de les privilégier. Les exemples qui suivront importeront les modules utiles sous la première forme (sauf mention explicite). WARNING : Ce qui suit n’est absolument pas important... 1.3.2 Modules sys et os Ces modules contiennent de nombreuses fonctionnalités permettant d’interagir avec les fichiers et le système d’exploitation. Ci-dessous quelques exemples. import os, sys dir(os) #pour avoir la liste de tous les objets de ce module dir(sys) os.getcwd() #pour conna^ ıtre le dossier de travail print(os.path.getsize("C:/users/daniel/fildefer.png")) #pour conna^ ıtre la taille d’un fichier en octets print(sys.float_info) 1.3.3 Enregistrer ses propres modules https://docs.python.org/3.5/tutorial/modules.html Un fichier où sont définies un certain nombre de fonctions (ou de classes) peut être employé comme un module. Voici la marche à suivre : • Écrire et enregistrer un fichier PYTHON (en notant le chemin menant au répertoire choisi), par exemple dans le répertoire (créé antérieurement) C:/Users/daniel/opus_mei 6
def safe_path(chemin): """Remplace les antislash \ par des slashs/. Echec avec les caractères spéciaux, comme \a, \b etc...""" new = "" for c in chemin: if c == ’\\’: #noter le doublement de l’antislash c =’/’ new += c return(new) if __name__=="__main__": print(safe_path(’D:\Daniel\python\graphiques’)) Noter la documentation et le test final (prévu pour n’être exécuté que si le script est actif : il ne le sera pas si le fichier est importé dans un autre script). • prendre connaissance du PYTHONPATH (liste des répertoires où les modules sont re- cherchés. import sys print(sys.path) #pas de parenthese, c’est un attribut de l’objet sys • si le répertoire choisi où est enregistré safe_path n’y figure pas, l’ajouter. sys.path.append(’C:/Users/daniel/opus_mei’) • on peut alors importer ce module comme n’importe quel module présent dans l’environ- nement (ici Pyzo), utiliser ses fonctions, consulter la documentation qu’on a prévue... from opus_mei import safe_path help(safe_path) print(safe_path(’E:\premiercours\TP1’)) L’extension de PYTHONPATH n’est pas permanente, elle ne dure que le temps de la session ouverte. 7
Chapitre 2 Ingéniérie numérique 2.1 Modules 2.1.1 Présentation numpy permet d’utiliser des tableaux de grande taille avec des procédures optimisées. http://www.numpy.org/ matplotlib permet toutes sortes de représentations graphiques. http://matplotlib.org/ scipy fournit des routines de résolution numérique approchée d’équations. http://docs.scipy.org/doc/ Ces trois modules s’utilisent conjointement : les tableaux de numpy servent de conteneur pour des données dont certaines peuvent être produites par scipy et être visualisées avec matplotlib. Voir le paragraphe 2.4.3 pour un exemple conséquent qui illustre la démarche générale : • on balaye l’intervalle choisi par la commande linspace qui produit un tableau t • on applique les fonctions choisies, ce qui produit des tableaux de même taille que t : x, y etc... • on peut extraire une partie d’un tableau par les techniques de slicing. Pour consulter la documentation que le concours Centrale met à votre disposition (et qui fixe le noyau des fonctions dont vous devez connaı̂tre l’existence) : http://www.concours-centrale-supelec.fr/CentraleSupelec/SujetsOral/PC et les rubriques : • Calcul matriciel • Réalisation de tracés • Analyse numérique • Polynômes • Probabilités 8
Table 2.1 – exemples d’importation import numpy as np import matplotlib.pyplot as plt module de réprésentation graphique import scipy.optimize as optim module de résolution approchée d’équations from scipy.integrate import quad commande d’intégration approchée from scipy.integrate import odeint commande de résolution approchée d’ODE from numpy.polynomial import Polynomial cf Python-polynomes.pdf de Centrale import numpy.random as rd cf Python-random.pdf de Centrale 2.1.2 Alias d’importation usuels Consulter la table 8.5 et le site de Centrale (ci-dessus). 2.2 numpy Obtenir de l’aide : consulter http://docs.scipy.org/doc/numpy/reference/generated/ numpy.info.html#numpy.info et aussi http://docs.scipy.org/doc/numpy/reference/generated/ numpy.lookfor.html#numpy.lookfor Quand on connaı̂t le nom de la fonction, par exemple load du module numpy précédemment importé 1 sous l’alias np, la commande help(np.load) donne accès à sa documentation. 2.2.1 Tableaux Le terme tableau sous-entend ici : tableau du module numpy donc les données doivent toutes être du même type. Ce type est soit déclaré à la création en même temps que la taille du tableau 2 , soit automa- tiquement déterminé par PYTHON en cas de création explicite (en général pour des tableaux de petite taille). import numpy as np tf = np.zeros(10, dtype=np.float) ti = np.array([1,2,3]) print(tf.dtype, ti.dtype) Les types reconnus par numpy (voir http://docs.scipy.org/doc/numpy/user/basics. types.html) sont rassemblés dans un tableau dont il suffit de connaı̂tre l’existence, comme pour de nombreuses données techniques sur Python. Plusieurs formats de nombres entiers, flottants, complexes et les booléens. 1. Il faut que le module soit importé pour consulter l’aide sur une de ses fonctions. 2. par défaut, c’est le type float 9
Table 2.2 – Types de données Data type Description bool Boolean (True or False) stored as a byte int Default integer type (same as C long ; normally either int64 or int32) intc Identical to C int (normally int32 or int64) intp Integer used for indexing (same as C ssize t ; nor- mally either int32 or int64) int8 Byte (-128 to 127) int16 Integer (-32768 to 32767) int32 Integer (-2147483648 to 2147483647) int64 Integer (-9223372036854775808 to 9223372036854775807) uint8 Unsigned integer (0 to 255) uint16 Unsigned integer (0 to 65535) uint32 Unsigned integer (0 to 4294967295) uint64 Unsigned integer (0 to 18446744073709551615) float Shorthand for float64 float16 Half precision float : sign bit, 5 bits exponent, 10 bits mantissa float32 Single precision float : sign bit, 8 bits exponent, 23 bits mantissa float64 Double precision float : sign bit, 11 bits exponent, 52 bits mantissa complex Shorthand for complex128 complex64 Complex number, represented by two 32-bit floats (real and imaginary components) complex128 Complex number, represented by two 64-bit floats (real and imaginary components) 10
2.2.2 Vectorisation Principe et avantages Le module numpy fournit des opérateurs et des fonctions qui s’appliquent élément par élément de tableaux de même forme, rendant inutile l’écriture de boucles (code plus com- pact) et débouchant sur un traitement interne beaucoup plus rapide (code plus efficace). import numpy as np from time import clock import math N = 10**6 t = np.linspace(0,1,N) r = np.zeros(N) debut = clock() for i in range(N): r[i] = np.exp(t[i]) #contresens!!! print(clock() - debut) debut = clock() for i in range(N): r[i] = math.exp(t[i]) print(clock() - debut) debut = clock() r = np.exp(t) print(clock() - debut) Opérateurs Les opérateurs sont +, -, *, /, ** Fonctions La liste des fonctions usuelles peut être consultée à http://docs.scipy.org/doc/numpy/ reference/ufuncs.html#available-ufuncs 2.2.3 Constructeurs Voir http://docs.scipy.org/doc/numpy/reference/routines.array-creation.html Comme toujours, si le module n’a pas importé toutes les fonctions (from numpy import * à proscrire) mais avec import numpy as np, les fonctions doivent être préfixées par np. 2.2.4 Vue et copie Deux différences essentielles avec les listes de Python : • les données sont typées, et certains types ne peuvent être affectés dans un tableau numpy (les caractères ou les séquences que sont les listes, chaı̂nes, tuples...) Par conséquent, si c’est nécessaire et possible, certaines données sont converties auto- matiquement : par exemple, si t = np.zeros(3, float), l’instruction t[0] = 3 conver- tit l’entier 3 en nombre flottant 3. avant l’affectation. Notamment sur les entiers typés de numpy, certaines limitations peuvent surprendre, comme les dépassements de capacité : 11
Table 2.3 – Routines sur les tableaux Nom Action Syntaxe array(data) type autodéterminé x = array([0., 1., 2.]) array(data, d) data et type déclarés y = array([0, 1, 2],float) linspace(d, f, n) n données entre d t = linspace(0, 1, 3) et f régulièrement es- pacées meshgrid(x, y, ...) réseau produit tt, xx = meshgrid(t, x) cartésien des vec- teurs x, y, ... zeros(s, d) initialisation à 0 de u = ones([2, 2], int) forme s et de type d ones(s, d) initialisation à 1 de v = zeros([2, 2], bool) forme s et de type d shape attribut (sans pa- u.shape renthèse !) forme size attribut taille u.size dtype attribut type des u.dtype données flatten() méthode qui convertit u.flatten() en ligne (première puis seconde...) reshape(s) méthode qui change la x.reshape([1, 3]) forme (la taille doit être invariante) liste = 2*[1, 1] #liste = [1, 1, 1, 1] t = 2*np.array([1, 1]) #t =[2, 2] print(t.dtype) print(t**50) #2**50 serait negatif! • le slicing ne produit pas une copie des données comme pour les listes, mais une vue c’est à dire un extrait du tableau initial. Pour une copie, il faut utiliser la méthode .copy(). liste = list(range(10)) lliste = liste[:] #copie lliste[0] = 5 #liste n’est pas modifie print(liste) t = np.array(liste) u = t[:] #vue complete de t u[0] = 5 #t est modifie print(t) v = t.copy() v[0] = 0 #t n’est pas modifie print(t) 2.2.5 Sauvegarde La commande np.save enrgegistre (à l’adresse choisie) les données rassemblées dans un tableau numpy. La taille du fichier est la taille des données (par exemple pour 1000 nombres float64, 8000 octets) augmentée de 80 octets (pour la description du tableau, du type de données). 12
import numpy as np import numpy.random as rd import os dir(rd) help(rd.rand) t = rd.rand(1000) print(t.mean()) np.save("H://cours3//hasard.npy", t) print(os.path.getsize("H://cours3//hasard.npy")) s = np.load("H://cours3//hasard.npy") print(s.mean()) 2.3 matplotlib Consulter http://matplotlib.org/1.3.0/api/pyplot_summary.html pour une liste de commandes. 2.3.1 Syntaxe minimale x(t) = sin(t) Représentation graphique de la courbe paramétrée . y(t) = sin t + π4 from math import pi import numpy as np import matplotlib.pyplot as plt t = np.linspace(0, 2*pi, 200) x = np.sin(t) y = np.sin(t + pi/4) plt.clf() #effacer le contenu de la fenetre graphique #ou plt.close() : fermer la fenetre plt.plot(x, y) plt.plot(x, -y) plt.show() Toutes les instructions graphiques sont représentées dans la même fenêtre (tant qu’elle active, c’est à dire ni effacée ni fermée), sauf si on crée explicitement plusieurs figures actives avec plt.figure(2) etc... On peut alors ”naviguer” d’une figure à l’autre : import matplotlib.pyplot as plt t = np.linspace(0, 2*np.pi, 100) plt.plot(t, np.sin(t)) plt.figure(2) plt.plot(t, np.cos(t)) plt.figure(1) plt.plot(t, abs(np.sin(t))) 2.3.2 Lignes de niveau Deux exemples : 13
import numpy as np import matplotlib.pyplot as plt #lignes de niveau de l’energie du pendule plt.close() x = np.linspace(-5, 5, 200) y = np.linspace(-1.5, 1.5, 100) xx, yy = np.meshgrid(x, y) z = yy**2/2-np.cos(xx) h = plt.contour(x,y,z,levels=np.linspace(-1, 2, 15),colors=’b’) plt.clabel(h, inline=1, fontsize=10) #lignes de niveau de (x**2+y**2)**2-2*x*y plt.close() x = np.linspace(-1, 1, 400) y = np.linspace(-1, 1, 400) xx, yy = np.meshgrid(x, y) z = (xx**2+yy**2)**2-2*xx*yy h = plt.contour(x,y,z,levels=np.linspace(-0.02, 0.11, 5)) plt.clabel(h, inline=1, fontsize=10) plt.axhline() plt.axvline() plt.axis(’equal’) WARNING : les trois paragraphes suivants ne sont pas fondamentaux... 2.3.3 Syntaxe enrichie • Différents styles de trait (ls ou linestyle) ls=’’ (pas de trait), ’-’ (trait continu), ’--’ (pointillé)... • Différents styles de point marker=’+’ (croix), ’o’ (cercle)... • Couleurs des traits et symboles color=’k’ (noir), ’r’,’b’ (rouge, bleu)... • Repère orthonormé : plt.axes(aspect=’equal’) • Légende (à l’intérieur de plot, déclarer un label, et l’écrire dans la fenêtre active avec legend()) Voir la documentation http://matplotlib.org/api/pyplot_summary.html et un exemple en 2.4.3. 2.3.4 Images La commande plt.imshow() permet de visualiser des images au format .png ou des ma- trices (RGB couleur, RGBA couleur avec transparence ou monochromes). emplies de flottants entre 0. et 1. Consulter pour plus de précisions http://matplotlib.org/1.3.0/api/pyplot_api.html#matplotlib.pyplot.imshow (et aussi l’exemple http://matplotlib.org/1.3.0/users/image_tutorial.html) création et visualisation de matrice Sélection des points (x, y) dont l’image z = f (x, y) = (x2 + y 2 )2 − 2xy est proche de 0. 14
import numpy as np import matplotlib.pyplot as plt n = 1000 epsilon = 0.01 c = 0 u, v = c+epsilon, c-epsilon x = np.linspace(-2, 2, n) y = np.linspace(2, -2, n) xx, yy = np.meshgrid(x, y, indexing=’xy’) z = (xx**2+yy**2)**2-2*xx*yy t = np.logical_or(z>u, z
2.4 scipy 2.4.1 Résoudre des équations À une inconnue Les fonctions newton, bissect et brentq du module scipy.optimize permettent la résolution approchée des équations à une seule inconnue. import scipy.optimize as optim import numpy as np import matplotlib.pyplot as plt def f(x): return(np.exp(x)-5*x) #exploration graphique x = np.linspace(-1, 5, 100) y = f(x) plt.plot(x, y) z = np.zeros(100, dtype = np.float) plt.plot(x, z) plt.show() #resolution par dichotomie print(optim.bisect(f, 0, 0.5)) print(optim.bisect(f, 2, 3)) #methode de Newton def fp(x): return(np.exp(x)-5) print(optim.newton(f, 0.2, fp)) print(optim.newton(f, 2, fp)) #resolution "rapide et sure" d’après scipy print(optim.brentq(f, 0, 0.5)) print(optim.brentq(f, 2, 3)) À plusieurs inconnues Les fonctions root et fsolve du module scipy.optimize permettent la résolution ap- prochée des équations à plusieurs inconnues. import scipy.optimize as optim def f(x): return([x[0]**2-2*x[0]*x[2]+5, x[0]*x[1]**2+x[1]*x[2]+1, 3*x[1]**2-8*x[0]*x[2]]) print(optim.root(f, [0.5, -2.5, 3.5])) print(optim.fsolve(f, [0.5, -2.5, 3.5])) 2.4.2 Intégrer Z 5 exp(t) Calcul approché de dt. 1 t from math import exp import numpy as np from scipy.integrate import quad def f(x): return(exp(-x)/x) print(quad(f, 1, 5))#renvoie le couple (estimation, incertitude) 16
(Fichier quadrat.py) 2.4.3 Résoudre une ODE Premier ordre Résolution de l’équation différentielle x0 (t) = x(t)2 − t avec les conditions initiales x(0) = −1 puis x(0) = 0, x(0) = 0.1, x(0) = 0.2, pour représenter des solutions lorsque t ∈ [0, 10]. import numpy as np from scipy.integrate import odeint import matplotlib.pyplot as plt def f(x,t): return(x*x-t) t = np.linspace(0, 10, 200) sol = odeint(f, -1, t) plt.grid() plt.plot(t, sol) plt.show() for k in range(3): sol = odeint(f, k/10, t) plt.plot(t, sol) plt.show() (Fichier odeOrdre1.py) Second ordre Résolution de l’équation différentielle du pendule simple x00 t) = − sin(x(t)) avec diverses conditions initiales (x(0) = 0, x0 (0) = 1 ou x(0) = 1, x0 (0) = 0 par exemple). Il faut l’écrire comme un système d’équations différentielles du premier ordre, en intro- duisant unedeuxième fonction inconnue v(t) = x0 (t), la vitesse. x0 (t) = v(t) Le système 0 s’écrit aussi u0 (t) = f (u(t), t) en posant u(t) = [x(t), v(t)] et v (t) = sin(x(t)) f (u, t) = (v(t), − sin(x(t)). 17
from math import sin import numpy as np from scipy.integrate import odeint import matplotlib.pyplot as plt #equation du pendule def f(xv, t): return([xv[1], -sin(xv[0])]) #equation linearisee def g(xv, t): return([xv[1], -xv[0]]) #graphes de solutions (t, x(t)) t = np.linspace(0, 10, 200) #numpy sol = odeint(f, [1, 0], t) #scipy->numpy sol_lin = odeint(g, [1, 0], t) plt.close() #matplotlib... plt.grid() plt.plot(t, sol[:,0], color=’k’, label=’ equation non lineaire’) plt.plot(t, sol_lin[:,0], ls=’--’, color=’b’, label=’equation linearisee’) plt.ylim(-1.5,1.1) plt.legend(loc=’lower right’) plt.show() plt.clf() #courbes de phase (x(t), v(t)) plt.plot(sol[:,0], sol[:,1], color=’k’, label=’ equation non lineaire’) plt.plot(sol_lin[:,0], sol_lin[:,1], ls=’--’, color=’b’, label=’equation linearisee’) xmin, xmax = plt.xlim() plt.xlim(xmin-0.1, xmax+0.1) ymin, ymax = plt.ylim() plt.ylim(ymin-0.5, ymax+0.1) plt.legend(loc=’lower right’) plt.show() sol est un tableau de 200 couples [x[i],v[i]]. sol[:,0] est le tableau des x[i] et sol[:,1] est celui des y[i] 18
Chapitre 3 Représentation binaire de données 3.1 Entiers en base 2 Les nombres entiers sont habituellement écrits en base 10 : notant c1,c2,...,cn des chiffres entre 0 et 9 et c0 un chiffre entre 1 et 9, la notation c0c1...cn désigne le nombre Xn ck 10n−k . La notation commence par les chiffres de poids fort (c0,c1...) et finit par le k=0 chiffre des unités cn. On peut écrire les nombres entiers dans toute base b (supérieure à 2) en utilisant des Xd chiffres qui seront compris cette fois entre 0 et b-1 : N = ck bd−k . k=0 ln N La longueur d + 1 de la représentation de N en base b est 1 + (puisque N est ln b X d d compris entre b et (b − 1)bk = bd+1 − 1). k=0 En raison du fonctionnement physique de l’ordinateur (qui s’appuie sur des circuits à 2 po- sitions), la base b=2 est omniprésente en informatique. Chaque donnée stockée sur un circuit est un bit (conventionnellement 0 ou 1), et un octet est un paquet de 8 bits consécutifs. La commande bin donne l’écriture binaire d’un nombre (qui est une chaı̂ne de caractères commençant par le préfixe ’0b’). On peut aussi écrire une fonction qui donne la liste des chiffres binaires. Il est commode de commencer par les unités, d’où si on veut respecter la convention (big endian) 1 qui commence par les poids forts, l’application finale de la méthode reverse(). print(bin(353)) def binaire(n): """Ecriture binaire, en écrivant à gauche les poids forts.""" liste = [] while n>0: liste.append(n%2) n = n//2 liste.reverse() return liste print(binaire(353)) print(hex(353)) 1. https://en.wikipedia.org/wiki/Endianness 19
Base hexadécimale Pour faciliter la lecture, la représentation binaire est souvent convertie en base 16 hexadécimale. Les chiffres sont alors 0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f. La conversion est aisée : par tranches de quatre chiffres binaires en commençant à droite depuis les unités, on évalue le nombre qui sécrit ainsi en base 2. Par exemple la tranche 1011 (écriture décimale du nombre 11) sécrit avec le chiffre hexadécimal b. Le nombre 353 s’écrit en binaire 1 0110 0001 (on a groupé les chiffres par 4) et en hexadécimal 161. La commande hex donne l’écriture hexadécimale d’un nombre (qui est préfixée par ’0x’). Opérations binaires L’addition de deux nombres en écriture binaire nécessite seulement de gérer des retenues. Par exemple l’addition de 353 et 68 : 1 0 1 1 0 0 0 0 1 1 0 0 0 1 0 0 ------------------ 1 1 0 1 0 0 1 0 1 En décalant vers la droite l’écriture binaire d’un nombre, on effectue la multiplication par 2. On réalise ainsi la multiplication par combinaison d’additions et de décalages. Par exemple 353 × 68 : 1 0 1 1 0 0 0 0 1 0 0 (4 fois 353) 1 0 1 1 0 0 0 0 1 0 0 0 0 0 0 (64 fois 353) ----------------------------- 1 0 1 1 1 0 1 1 1 0 0 0 1 0 0 Type uint de numpy Le module numpy propose un type d’entier non signé (unsigned integer) encodé sur 4 oc- tets (d’où la précision possible uint32 pour le distinguer d’autres types existants). Il faut être conscient des conséquences d’un dépassement de capacité : si un décalage (ou une retenue) fait dépasser la capacité d’encodage du résultat, les bits excédentaires sont tout simplement effacés ! Seuls les nombres compris entre 0 et 232 −1 = 4294967295 sont correctement encodés. t = np.array([2, 3], np.uint) for k in range(5): t = t*t print(t) In [40]: (executing lines 46 to 49 of "cours_encodage.py") [4 9] [16 81] [ 256 6561] [ 65536 43046721] [ 0 3793632897] #faux In [41]: print(2**32, 3**32, 3**32%2**32) 4294967296 1853020188851841 3793632897 3.2 Entiers relatifs Le module numpy propose (entre autres) un type d’entier relatif int32 qui est aussi abrégé en int, qui permet d’encoder les entiers compris entre −231 et 231 − 1. 20
• un nombre positif ou nul n est encodé directement comme l’entier non signé n • un nombre négatif n est encodé comme l’entier non signé n + 232 Exemples : • 123 est encodé sur un octet par 01111011 (et sur 4 octets en commençant par 3 octets de zéros...) In [1]: bin(123) Out[1]: ’0b1111011’ • -123 est encodé sur quatre octets comme 232 − 123 =4 294 967 173, ce qui donne 11111111 11111111 11111111 10000101 Le passage à l’opposé se traduit par – le complément à 2 (on remplace tous les 0 par 1 et réciproquement) : (123) 00000000 00000000 00000000 01111011 11111111 11111111 11111111 10000100 – suivi de l’addition de 1 (ce qui peut entraı̂ner une succession de retenues) Avec ces conventions, les dépassements de capacité sont encore plus surprenants que pour les entiers non signés. s = np.array([2, 3], np.int) for k in range(5): s = s*s print(s) In [52]: (executing lines 54 to 57 of "cours_encodage.py") [4 9] [16 81] [ 256 6561] [ 65536 43046721] [ 0 -501334399] In [53]: 3**32 Out[53]: 1853020188851841 In [54]: 2**32+s[1] #entier uint encodant s[1]
3.3 Flottants https://docs.python.org/3/library/stdtypes.html?highlight=float#float.hex L’implémentation des nombres flottants suit la norme internationale IEEE754 de 1985. 2 Le type float (par défaut) de PYTHON est encodé sur 8 octets (float64) : le premier bit pour le signe, les 11 bits suivants pour l’exposant, les 52 bits restants pour la mantisse : n = s2e m. A cause du bit de signe s, 0 est encodé deux fois. L’exposant e est un entier compris entre -1022 et 1023. Il est encodé sous la forme uint(e+1023). On notera que les encodages par uint(0) et uint(2047) (211 − 1 = 2047) sont libres : ils servent à encoder le zéro et l’infini. 52 X dk La mantisse est un nombre compris entre 1 et 2. m = 1 + k est encodé par les 52 bits k=1 2 dk . Nombres représentables • Le plus grand nombre (notons le MAX) représentable est 52 ! X 1 −53 21023 1 + 1024 k = 2 1 − 2 ≈ 1.7976931348623157e + 308 k=1 2 • le plus petit nombre (notons le min) représentable est 2−1022 ≈ 2.2250738585072014e − 308. • Zéro est encodé par un bit de signe ± suivi de 63 bits nuls. Un bit de signe ± suivi de 11 bits nuls puis de 52 bits non tous nuls n’est pas un nombre (ce serait l’encodage d’un nombre inférieur à min). On parle parfois de zéro non normalisé. • Les deux infinis sont encodés par un bit de signe, 11 bits égaux à 1 suivis de 52 bits nuls. Un bit de signe ± suivi de 11 bits égaux à 1 puis de 52 bits non tous nuls n’est pas un nombre (ce serait l’encodage d’un nombre supérieur à MAX). PYTHON les désigne par le nom Nan (Not a number). Toutes ces informations sont accessibles dans le module sys. import sys print(sys.int_info) print(sys.float_info) In [59]: (executing lines 60 to 63 of "cours_encodage.py") sys.int_info(bits_per_digit=30, sizeof_digit=4) sys.float_info(max=1.7976931348623157e+308, max_exp=1024, max_10_exp=308, min=2.2250738585072014e-308, min_exp=-1021, min_10_exp=-307, dig=15, mant_dig=53, epsilon=2.220446049250313e-16, radix=2, rounds=1) 2. https://en.wikipedia.org/wiki/IEEE_floating_point 22
Arrondis La représentation d’un nombre est arrondie au plus proche. Par exemple, le nombre décimal 0.1 n’est pas représenté exactement : son signe est +, son exposant est -4 et sa mantisse est (en hexadécimal) 999999999a. 1 En effet 1 ≤ 24 × 0.1 = 1.6 < 2, d’où l’exposant -4, et 1.6 = 1 + + 0.1, ce qui 2 1 1 donne le développement illimité 0.1 = 2−4 1 + + 2−4 1 + + . . . . La mantisse répète 2 2 indéfiniment la succession de bits 1001 c’est à dire 9. Sa représentation la copie 13 fois et le 1 reste est plus grand que 53 d’où l’arrondi du treizième bloc à 1010 c’est à dire a. 2 print(float.hex(0.1)) In [62]: (executing line 75 of "cours_encodage.py") 0x1.999999999999ap-4 Cet arrondi a des conséquences dès qu’on effectue quelques additions comme ci-dessous : x = 0.1 for k in range(9): x = x+0.1 print(x) In [63]: (executing lines 70 to 73 of "cours_encodage.py") 0.2 0.30000000000000004 0.4 0.5 0.6 0.7 0.7999999999999999 3.4 Caractères https://docs.python.org/3/howto/unicode.html http://pyvideo.org/video/289/pycon-2010--mastering-python-3-i-o Caractères ASCII Un alphabet réduit à 128 caractères est encodé sur un octet : l’alphabet ASCII. Certains de ses caractères ne sont pas imprimables (ils ne servent qu’à communiquer en langage ma- chine). Les caractères accentués chers aux français ne sont pas disponibles. Théoriquement, un octet permet d’encoder 256 caractères différents. Cette possibilité est exploitée par l’alphabet Latin-1 (défini par une norme internationale) ou l’alphabet propriétaire de Microsoft cp-1252. La limitation à 128 caractères ASCII provient de l’utilisation du huitième bit comme bit de parité, à des fins de détection d’erreur : ainsi, la lettre a, qui porte le numéro 97 est encodée par les 7 bits 1100001 auquel on ajoutait le bit de parité 1. En cas de transmission erronée, si un seul bit est faux, l’erreur sera décelée. Voir le thème Codage correcteur d’erreurs. Unicode La mondialisation des échanges a rendu indispensable une normalisation de l’encodage de tous les caractères utilisés à la surface de la terre. C’est le rôle de l’Unicode. Comme dans la table ASCII, chaque caractère est repéré par un numéro. Les 128 premiers numéros sont ceux de la table ASCII, et la numérotation est ouverte pour accueillir si nécessaire de nouveaux caractères : actuellement plus de cent mille caractères... L’alphabet grec est repéré par la tranche 945-969, et le symbole de l’euro par 8364. 23
Figure 3.1 – Les 128 caractères ASCII alpha = "" for k in range(945, 970, 1): alpha += chr(k) print(alpha) print(chr(8364)) Encodages L’encodage courant de la norme Unicode s’appelle utf-8. Avec cette méthode, les caractères ASCII sont encodés sur un octet, les autres caractères usuels sont encodés sur deux octets, et seuls des caractères exotiques nécessitent trois ou quatre octets. • Sur un octet, le code ASCII est précédé du bit 0. Ainsi la lettre a est encodée par 01100001. • Sur deux octets, le numéro est représenté par une séquence de 11 bits, ce qui permet de représenter les numéros de 128 à 2047. Les 5 bits de tête sont précédés du préfixe 110 3 , et les six bits suivants sont précédés du préfixe 10 4 . La lettre accentuée é a pour numéro 233, qui sécrit (faire la succession de divisions euclidiennes par 2 en notant les restes : 233 = 2 × 116 + 1, 116 = 2 × 58 etc...) sur 11 bits : 00011101001. L’encodage sur 2 octets est donc 110 00011 10 101001 5 . 3. qui indique l’octet ouvrant un encodage sur deux octets 4. qui indique que cet octet prolonge un encodage déjà ouvert 5. les préfixes ont été détachés des autres bits. 24
• Vous irez lire la page suivante pour trois ou quatre octets : http://en.wikipedia.org/wiki/UTF-8 où il est expliqué pourquoi le symbole de l’euro est encodé sur trois octets : 1110 0010 10 000010 10 101100 • Un octet qui commence par 0 encode un caractère ASCII. Un octet qui commence par 110 est suivi d’un octet qui commence par 10. Un octet qui commence par 1110 est suivi de deux octets qui commencent par 10. In [8]: ord(’é’) Out[8]: 233 In [9]: bin(233) Out[9]: ’0b11101001’ In [10]: hex(233) Out[10]: ’0xe9’ In [11]: ’é’.encode(’latin-1’) Out[11]: b’\xe9’ In [12]: ’é’.encode(’utf-8’) Out[12]: b’\xc3\xa9’ 3.5 Conclusion La représentation binaire de nombreux autres objets resterait à étudier. Elle est souvent compliquée par la multiplicité de formats concurrents (par exemple pour les images fixes : pgm, png, jpeg entre autres...). Les alphabets qui ont précédé l’Unicode (et qui sévissent encore...) illustrent les difficultés transitoires jusqu’à ce qu’une solution pérenne et univer- sellement acceptée s’impose. Même la représentation des nombres (ne serait-ce que par la problématique big or little endian) est compliquée. Les programmes se chargent de tout (encodage et décodage) et on n’a pas besoin de connaı̂tre les détails de la représentation binaire. Cependant, particulièrement pour les fi- chiers .txt, l’absence de précision de l’encodage (essentiellement Latin-1 ou utf-8) est la source de nombreuses erreurs. Trois points à retenir : • tout objet est codé sous la forme d’une séquence de bits groupés par 8 en octets. • la méthode d’encodage doit être connue pour qu’on puisse interpréter le contenu d’une séquence de bits (c’est à dire la décoder). L’extension d’un fi- chier (par exemple .txt, .png, .jpeg) sert à désigner quel programme a été utilisé pour encoder et surtout quel programme appeler pour décoder. • un nombre flottant (standard) est codé sur 8 octets 25
Chapitre 4 Fonctions 4.1 Généralités 4.1.1 En tête La définition d’une fonction (ou procédure) commence par le nom réservé def : def nom(arguments): suivi d’un bloc indenté qui contient les instructions. Il est utile de documenter 1 ses fonctions, pour faciliter leur prise en main par d’autres utilisateurs (ou pour soi-même quand on la reprend après quelque temps). Consulter l’aide sur une fonction de PYTHON (par exemple help(list.append)). Un commentaire encadré entre deux occurrences de triple guillemets (""") peut s’étendre sur plusieurs lignes. 4.1.2 Valeur de retour L’instruction return variables met fin à l’exécution et retourne dans le cours de la ses- sion la donnée qui suit return (des données multiples doivent être rassemblées dans un tuple, une liste, une chaı̂ne de caractères ou un tableau). def div_euclid(a, b): """Calcule le quotient et le reste dans la division euclidienne de a par b, entiers naturels.""" q, r = 0, a while r>=b: #invariant de boucle b*q+r égal à a q, r = q+1, r-b return (q, r) print(div_euclid(27, 5)) La séquence d’instructions peut comporter plusieurs return (dans le cas d’instructions conditionnelles), mais bien sûr lors d’une exécution, un seul est exécuté, mettant un terme en retournant des valeurs. La séquence peut aussi ne pas comporter de return. Dans ce cas, il revient au même de d’écrire juste avant la fin du bloc l’instruction return None. Les fonctions sans valeur de re- tour explicite retournent en effet le nom réservé None. On les appelle parfois des procédures. Par exemple, la méthode append de la classe d’objets list est une procédure. Vous serez donc déçu par le code suivant : L = [] #liste vide L = L.append(1) print(L) L.append(2) 1. https://www.python.org/dev/peps/pep-0257/ 26
en revanche, celui-ci fonctionne très bien : L = [] for k in range(5): L.append(k) print(L) même si PYTHON offre des syntaxes plus brèves comme : L = list(range(5)) L = [k for k in range(5)] On doit de même distinguer clairement return (qui met un terme à la fonction en four- nissant une valeur de retour) et print qui est une commande ordinaire qui produit un effet (affichage sur le périhpérique écran). 4.2 Portée des variables 4.2.1 Nom de variable Toute succession de lettres et de chiffres ou de symboles (notamment l’underscore _) qui commence par une lettre est une nom valable et peut servir de référence à une adresse mémoire après affectation. Ainsi 007JB = ’Daniel Craig’ déclenche un message d’erreur, alors que ce qui suit est correct : JB007 = ’Daniel Craig’ print(id(JB007)) #adresse mémoire Certains noms sont cependant réservés au langage Python. En voici la liste : Logique : True, False, None, and, or, not, is Construction : class, def, lambda,global, nonlocal Contrôle : if, elif, else, while, for, in, return, assert, break, continue, pass, try, except, finally, raise, yield Gestion de modules, de fichiers, de noms : from, import, as, with, del Il est conseillé de choisir des noms explicites, qui facilitent la compréhension du pro- gramme. Par exemple longueur plutôt que l etc... Dans cette optique, les raccourcis suivants sont appréciables si le nom de la variable est plus long que x (mais ils nuisent à la lisibilité et peuvent avoir des effets difficilement prévisibles : voir 4.3.3 x = 0 x += 1 #remplace x = x + 1 x *= 2 #x = x * 2 x **= 3 #x = x**3 x %= 3 #x = x%3, reste modulo 3 x //= 3 #x = x//3, division euclidienne 27
4.2.2 Variables globales Les affectations dans la session définissent des variables globales qui jouent le rôle de constantes. a = 3 Ces variables sont associés à leur valeur dans toute instruction print(a+5) et dans toute fonction où une variable locale qui porte le même nom ne lui fait pas écran (voir le paragraphe suivant) def f(x): return a*x print(a, f(1)) 4.2.3 Liaison dynamique Simple et IMPORTANT : Si une variable globale intervient dans le corps d’une fonction, la valeur qui sera prise en compte lors d’une exécution est la valeur au moment de l’exécution, qui n’est pas nécessairement la même qu’au moment de la définition de cette fonction. On parle de liai- son dynamique (tous les langages ne fonctionnent pas ainsi). a = 3 def f(x): """Multiplie x par la valeur courante de a.""" return a*x print(f(1)) #3 a = 5 #la modification sera prise en compte pour toute execution ulterieure print(f(1)) #5 4.2.4 Variables locales Toute variable affectée dans le corps d’une fonction est locale : on ne peut l’utiliser dans la session principale, et une variable du même nom dans la session principale (ou dans d’autres fonctions) est totalement indépendante de cette variable locale. def f(x): """Multiplie x par 3.""" b = 3 return b*x print(f(1)) #3 print(b) #erreur, sauf si une variable globale b a ete definie anterieurement Une variable locale fait écran au sein de la fonction à toute variable globale de même nom. a = 3 def g(x): """Multiplie x par 2.""" a = 2 #variable locale return a*x print(a, g(1)) #3, 2 28
Il est déconseillé de changer ce comportement en déclarant explicitement des variables globales (avec le nom réservé global) dans les fonctions. a = 3 def f(x): """Affecte 2 a la variable a et multiplie x par 2.""" global a a = 2 return a*x print(a, f(1)) #2, 2 4.2.5 Fonctions locales Pour définir une fonction complexe, il est plus sûr de fractionner le code en un certain nombre de fonctions élémentaires. Ces fonctions peuvent être globales, mais aussi locales à la fonction appelante. L’avantage de fonctions locales réside dans le passage automatique des paramètres de la fonction appelante à ces fonctions locales. Nous illustrons cela sur l’exemple de la recherche d’une chaı̂ne au sein d’un texte. Algorithme Etant donné deux chaı̂nes de caractères mot et texte, on examine pour chaque caractère de texte si une occurence de mot commence à ce caractère. Avec slicing La technique du slicing permet de créer une sous-chaı̂ne de caractères (de- puis l’indice de début jusqu’à l’indice de fin exclu). def recherche(mot, texte): """Decide si la chaine de caracteres ’mot’ est une sous-chaine de la chaine ’texte’.""" p, q = len(mot), len(texte) for i in range(q-p+1): if mot == texte[i:i+p]: #slicing return True return False #des tests u=’cours’ v="le concours de l’ecole polytechnique" print(recherche(u, v)) u=’tecnique’ print(recherche(u, v)) u=’technique’ print(recherche(u, v)) Fonction globale On peut remplacer le test d’égalité par une fonction globale d’égalité entre deux chaı̂nes. La fonction de recherche utilise encore le slicing (donc crée une chaı̂ne annexe). Pour varier un peu, la fonction recherche_bis retournera, si elle le trouve, le premier indice dans le texte où commence une occurence du mot. 29
def egal(mot1, mot2): """Teste si deux chaines de caracteres sont egales.""" p, q = len(mot1), len(mot2) if p != q: return False else: for i in range(p): if mot1[i] != mot2[i]: return False return True def recherche_bis(texte, mot): """Renvoie l’indice de debut de la premiere occurence de ’mot’ dans ’texte’ et None sinon.""" p, q = len(texte), len(mot) for debut in range(p-q+1): mot2 = texte[debut:debut+q) if egal(mot, mot2): return debut return None Fonction locale Idem avec une fonction locale. Le code de la fonction egal est allégé, et il n’y a plus besoin de chaı̂ne annexe (produite par slicing). def recherche_ter(texte, motif): """Renvoie l’indice de debut de la première occurence de motif dans texte et None sinon.""" p, q = len(texte), len(motif) def egal(debut): #fonction locale: texte, motif, p, q sont connus dans la procedure appelante donc de la procedure ’egal’ (a moins qu’une variable locale a ’egal’ porte le meme nom et fasse ecran...) for k in range(q): if motif[k] != texte[debut+k]: return False return True for i in range(p-q+1): if egal(i): return i return None Exercice Modifier le code pour qu’il n’y ait plus qu’un return par fonction et que les boucles (dont le nombre d’itérations dépend de l’instance traitée) soient contrôlées par while au lieu de for. Pour cela, vous aurez besoin d’un booléen qui répond à la question : ”Faut-il continuer ?”. 4.3 Passage des arguments 4.3.1 Passage par valeurs Les valeurs des arguments d’une fonction sont copiées lors d’une exécution. Les argu- ments formels peuvent être utilisés comme des variables locales (c’est à dire affectées dans le corps de la fonction). Ci-dessous des exemples caricaturaux où on s’acharne à noter avec le même nom de nombreux objets différents. 30
a = 5 def carre(a): """Elève le nombre a au carré.""" a = a*a return(a) #les deux lignes peuvent etre résumées par return a*a print(a, carre(a)) a = list(range(5)) def repete(a): """Concatène la liste a avec elle m^ eme.""" a = 2*a return a #idem return 2*a résume les deux lignes print(a, repete(a)) import numpy as np a = np.ones(5, int) def double(a): """Multiplie tous les éléments d’un tableau par 2.""" a = 2*a return a #idem... print(a, double(a)) 4.3.2 Fonction qui modifie ses arguments On vient d’expliquer que les arguments passés à une fonction ne sont pas modifiés (sauf accident, voir le paragraphe suivant). Ce comportement est ce qu’on souhaite en général : la fonction crée de nouveaux objets, de nouvelles données sans modifier ce qui existait antérieurement dans la mémoire (globale). Cependant, notamment pour le tri de données (qui peuvent remplir des tableaux très volu- mineux), on peut souhaiter des méthodes qui trient sur place, c’est à dire qui rangent les données dans le tableau initial sans créer une nouvelle structure de données aussi volumi- neuse. L’espace mémoire du tableau fourni en argument est modifié par la fonction de tri jusqu’à donner en sortie le tableau trié. C’est possible pour les structures mutables que sont les listes et les tableaux, en manipulant les éléments auxquels on accède par la notation indicée. 2 Exemple du tri-bulle : pour ranger dans l’ordre croissant un tableau t de nombres de taille n Principe on effectue des parcours successifs de t au cours desquels on compare deux données d’indices consécutifs en les remettant dans l’ordre si nécessaire. Après un parcours com- plet, on est seulement assuré que le plus grand nombre est en dernière position, on n’a plus qu’à parcourir n-1 données, et ainsi de suite. Algorithme Entrées : t est un tableau de taille n - pour p (le nombre de données restant à ranger) variant entre n et 2 - pour i variant de 1 à p-1, comparer t[i-1] et t[i] et les ranger dans l’ordre croissant Simulation Avec 5 données aléatoires : p=5, i=1, t=[7, 8, 5, 9, 1] 2. Visualisation de l’exécution sur le site : http://pythontutor.com/ 31
p=5, i=2, t=[7, 5, 8, 9, 1] p=5, i=4, t=[7, 5, 8, 1, 9] p=4, i=1, t=[5, 7, 8, 1, 9] p=4, i=3, t=[5, 7, 1, 8, 9] p=3, i=2, t=[5, 1, 7, 8, 9] p=2, i=1, t=[1, 5, 7, 8, 9] Code itératif def tri_bulle(t): n = len(t) for p in range(n, 1, -1): for i in range(1, p): if t[i-1]>t[i]: t[i-1], t[i] = t[i], t[i-1] return None 4.3.3 Attention aux raccourcis WARNING : Inutile de lire ce paragraphe si vous n’employez jamais les raccourcis du genre x += 1. On l’a vu dans le paragraphe 4.3.1 : les références passées en argument d’une fonction ne sont pas modifiées par l’exécution de cette fonction (même si l’objet référencé est mutable, comme les listes ou les tableaux. Ce n’est plus vrai avec les raccourcis +=,*=,**= etc...Si on veut utiliser les raccourcis avec des objets mutables, il faut commencer par faire explicitement une copie ! Dans les exemple ci-dessous, on a évité toute ressemblance entre paramètre formel (x)et référence globlale (a), pour souligner que les différences viennent uniquement des raccourcis. a = list(range(5)) def ddouble(x): x *= 2 return x print(a, ddouble(a)) #a est modifie!!! a = list(range(5)) def dddouble(x): y = x y *= 2 return y print(a, dddouble(a)) #la aussi!!! Rappelons que listes et tableaux numpy ne se copient pas de la même façon. Pour être certain de ne pas employer de fonction avec des objets auxquels elle n’est pas destinée, il suffit de concevoir des méthodes (voir 4.4). On parlera peut-être plus tard de programmation orientée objet... 32
a = list(range(5)) def Double(a): a = a[:] #ou a=a.copy() a *= 2 return a print(a, Double(a)) a = np.ones(5, int) def DDouble(a): a = a.copy() #a[:] est une vue pour un tableau a *= 2 return a #return(2*a) remplace les trois lignes print(a, DDouble(a)) 4.3.4 Valeurs par défaut Les apprentis-sorciers fourbiront leurs armes en lisant https://docs.python.org/3/tutorial/ controlflow.html#more-on-defining-functions def genere_chaine(debut=65, fin=91): """Génère la cha^ ıne des caractères de numéro compris entre debut (inclus, valant 63 par défaut) et fin (exclu, valant 90 par défaut).""" c = ’’ #cha^ ıne vide for i in range(debut, fin): c = c + chr(i) return c print(genere_chaine()) print(genere_chaine(fin=120)) 4.4 Méthodes pour objets PYTHON est un langage orienté objet : pour chaque classe d’objets sont définies des fonc- tions qu’on appelle dans ce cas des méthodes et des données annexes qu’on appelle des at- tributs. La syntaxe utilise le point. Vous l’avez déjà employée avec les listes : liste.append(element) Autre exemple : les tableaux de numpy sont des objets dotés de l’attribut shape et des méthodes flatten (sans argument, qui donne une copie) et reshape (qui donne une vue, pas un tableau indépendant). import numpy as np m = np.ones([6, 8], int) print(m.shape) #attribut p = m.flatten() #methode q = m.reshape([12, 4]) q[0] = 0 #modifie la premiere ligne de q print(m) #m a ete modifie print(p) #pas p qui est une copie 33
Chapitre 5 La structure de Pile 5.1 Structures de données Une certain nombre de structures de données sont disponibles en PYTHON (ou dans le module numpy pour la dernière) : • les listes • les tuples • les chaı̂nes de caractères • les dictionnaires (que nous n’utiliserons pas) • les tableaux Chacune de ces structures est riche de nombreuses fonctionnalités. Selon le problème traité, l’une d’entre elles sera choisie parce qu’elle est plus commode ou plus efficace que les autres. Le tableau ci-dessous présente uniquement les commandes sur les listes qui vont nous être utiles pour une implémentation des piles. Il peut être complété par la consultation de https: //docs.python.org/3/tutorial/datastructures.html#more-on-lists Table 5.1 – Méthodes de listes Méthode/fonction Action Syntaxe len retourne la longueur len(liste) append ajoute un élément à la fin de la liste liste.append(x) pop supprime et retourne l’élément en fin de liste liste.pop() 5.2 La structure de pile La structure de pile n’est pas implémentée en PYTHON . C’est une structure très primitive qui, dans sa version minimaliste, ne supporte que trois ac- tions : la création, l’empilement et le dépilement. Créer une pile initialement vide. 34
Vous pouvez aussi lire