.. _cours: Initiation à Python ################### .. contents:: Table des matières :local: .. 1 Types de base 2 Structures de programmation 3 Les chaînes de caractères 3.1 Indexation 3.2 Sous-liste (*slice*) 3.3 Méthodes 3.4 Formatage 4 Objets itérables 5 Fonctions 6 Bibliothèques et scripts 6.1 Bibliothèques externes 6.2 Bibliothèques personnelles et scripts 7 Exceptions 7.1 Gestion des exceptions 7.2 Genèse des exceptions 8 Programmation Orientée Objet 9 Entrées-sorties 9.1 Intéractif 9.2 Fichiers texte Types de base ============= Les principaux :doc:`types de base ` nativement intégrés au langage Python sont: .. index:: None - `None` (rien) .. index:: pair: itérables; str - **Chaînes de caractères**: :class:`str` - Définies entre (simples ou triples) apostrophes `'` ou guillemets `"` >>> 'Calvin' 'Calvin' >>> "Calvin'n'Hobbes" "Calvin'n'Hobbes" >>> """Long texte ... sur deux lignes""" 'Long texte\nsur deux lignes' - Conversion: `str(3.2)` >>> str(3.2) '3.2' .. index:: pair: type numérique; bool pair: type numérique; int pair: type numérique; float pair: type numérique; complex - **Types numériques**: - :class:`bool` *booléen* (vrai/faux): `True`, `False`, `bool(3)` - :class:`int` *entier relatif* (pas de valeur limite explicite, correspond *au moins* au `long` du C): `-2`, `int(2.1)`, `int("4")` - :class:`float` *réel* (entre ±1.7e±308, correspond au `double` du C): `2.`, `3.5e-6`, `float(3)` - :class:`complex` *complexe*: `1 + 2j` (sans espace avant le `j`), `5.1j`, `complex(-3.14)`, `complex('j')` >>> 5 / 2 # / → division réelle (par défaut dans Python 3.x) 2.5 >>> 6 // 2.5 # // → division euclidienne explicite 2.0 >>> 6 % 2.5 # % → reste de la division euclidienne 1.0 >>> (1 + 2j)**-0.5 # ** → puissance (entière, réelle ou complexe) (0.5688644810057831-0.3515775842541429j) .. index:: pair: itérables; list pair: itérables; tuple pair: itérables; set pair: itérables; dict range type isinstance - **Objets itérables**: - :class:`list` *liste* ordonnée d'éléments quelconques, regroupés entre `[]`, p.ex. `['a', 3, [1, 2], 'a']` - :class:`tuple` *liste immuable* ordonnées d'éléments quelconques, regroupés entre `()`, p.ex. `(2, 3.1, 'a', [])` (selon les conditions d'utilisation, les parenthèses ne sont pas toujours nécessaires) - :class:`dict` *liste à clés* ou *dictionnaire*, défini par :samp:`{{clé}: {valeur}}`, p.ex. `{'a':1, 'b':[1, 2], 3:'c'}` - :class:`set` *ensemble* non ordonné d'éléments uniques, défini entre `{}` (mais sans clé), p.ex. `{1, 2, 3, 2}` >>> l = ['a', True] # Définition d'une liste >>> x, y = 1, 2.5 # Affectations multiples via tuples (les parenthèses ne sont pas nécessaires) >>> list(range(5)) # Construction d'une liste de 5 entiers commençant par 0 [0, 1, 2, 3, 4] >>> l + [x, y] # + → concaténation de listes ['a', True, 1, 2.5] >>> {2, 1, 3} | {1, 2, 'a'} # | → union d'ensembles (non-ordonnés) {'a', 1, 2, 3} .. Attention:: En Python 3, :class:`range` n'est plus un constructeur de liste, mais un *itérateur*, qui doit être converti en liste explicitement: >>> range(3) # Itérateur range(0, 3) >>> list(range(3)) # Conversion en liste [0, 1, 2] - :samp:`type({obj})` retourne le type de l'objet, :samp:`isinstance({obj}, {type})` teste le type de l'objet. >>> type(l) >>> isinstance(l, tuple) False .. rubric:: Liens: - `The Floating Point Guide `_ - `What Every Computer Scientist Should Know About Floating-Point Arithmetic `_ Structures de programmation =========================== - Le langage Python est sensible aux majuscules (*case sensitive*): `a` ≠ `A`. - Les blocs sont définis par l'**indentation** (en général par pas de quatre espaces) [#braces]_. .. Attention:: Évitez autant que possible les caractères de tabulation, source de confusion. Configurez votre éditeur de texte pour qu'il n'utilise que des espaces. - Une instruction par ligne *en général* (ou instructions séparées par `;`). - Les commentaires commencent par `#`, et s'étendent jusqu'à la fin de la ligne. - **Assignation**: une valeur est assignée à une variable par :samp:`{variable} = {valeur}`. Les assignations peuvent être multiples, s'il y a le même nombre d'éléments de part et d'autre de l'affectation, p.ex. `x, y = 1, 2` (mais pas `x, y = 1, 2, 3`). Une variable peut évidemment être redéfinie, p.ex. `n = n + 1`, mais pour ce genre de modification, il est préférable d'utiliser les assignations *augmentées*, p.ex. `n += 1`, qui réalisent l'opération *sur place* (c.-à-d. sans redéfinir une nouvelle variable de même nom). .. index:: pair: type numérique; bool opérateur ternaire (... if ... else ...) - **Expression booléenne**: une condition est une expression s'évaluant à `True` ou `False`: - `False`: test logique faux (p.ex. `3 > 4`), valeur nulle, chaîne vide (`''`), liste vide (`[]`), etc. - `True`: test logique vrai (p.ex. `2 in [1, 2, 3]`), toute valeur ou objet non nul (et donc s'évaluant par défaut à `True` *sauf exception*) - Tests logiques: `==`, `!=`, `>`, `>=`, `in`, etc. .. Attention:: Ne pas confondre « `=` » (affectation d'une variable) et « `==` » (test logique d'égalité). - Opérateurs logiques: :keyword:`and`, :keyword:`or`, :keyword:`not` >>> x = 3 >>> not ((x <= 0) or (x > 5)) True >>> 0 < x <= 5 # Conditions chaînées True - Opérateur ternaire (:pep:`308`): :samp:`{value} if {condition} else {altvalue}`, p.ex. >>> y = x**0.5 if (x > 0) else 0 # Retourne sqrt(max(x, 0)) .. index:: if ... elif ... else ... - **Expression conditionnelle**: :samp:`if {condition}: ... [elif {condition2}: ...] [else: ...]`, p.ex.:: if (i > 0): # Condition principale print("positif") elif (i < 0): # Condition secondaire (si nécessaire) print("négatif") else: # Cas final (si nécessaire) print("nul") .. index:: for ... in ... continue break - **Boucle for**: :samp:`for {element} in {iterable}:`, s'éxecute sur chacun des *éléments* d'un objet *itérable*: >>> for val in ['un', (2, 3), 4]: # Itération sur une liste de 3 éléments ... print(val) un (2, 3) 4 >>> nv, nc = 0, 0 # Nb de voyelles et de consonnes >>> for l in "supercalifragilisticexpialidocious": ... if l in "aeiouy": ... nv += 1 ... else: ... nc += 1 >>> print(nv + nc, "lettres,", nv, "voyelles,", nc, "consonnes") 34 lettres, 16 voyelles, 18 consonnes Il est possible de dépaqueter à la volée les valeurs composées de l'itérable: >>> pairs = [("Calvin", "Hobbes"), ("Boule", "Bill"), ("Wallace", "Gromit")] >>> for elt1, elt2 in pairs: # dépaquetage des paires à la volée ... print(elt1, "et", elt2) Calvin et Hobbes Boule et Bill Wallace et Gromit >>> d = dict(nom="Bond", prénom="James", no=7) # Définition d'un dictionnaire >>> for key, val in d.items(): # Itération sur les paires (clé, valeur) ... print(key, val) nom Bond prénom James no 7 - :keyword:`continue`: interrompt l'itération courante, et reprend la boucle à l'itération suivante, - :keyword:`break`: interrompt complètement la boucle. .. Note:: la logique des boucles Python est différente des langages C[++]/fortran, pour lesquels l'itération porte sur les *indices* plutôt que sur les éléments eux-mêmes. .. index:: while break - **Boucle while**: :samp:`while {condition}:` se répéte tant que la *condition* est vraie, ou jusqu'à une sortie explicite avec :keyword:`break`. .. Attention:: aux boucles infinies, dont la condition d'exécution reste invariablement vraie (typiquement un critère de convergence qui n'est jamais atteint). On peut toujours s'en protéger en testant *en outre* sur un nombre maximal (raisonnable) d'itérations: .. code-block:: python niter = 0 while (error > 1e-6) and (niter < 100): error = ... # A priori, error va décroître, et la boucle s'interrompre... niter += 1 # ... mais on n'est jamais assez prudent! if niter == 100: # Ne pas oublier de tester l'absence de convergence!!! print("Erreur de convergence!") .. Note:: Il n'y a pas en Python d'équivalent natif à l'instruction `switch` du C [#match]_, ni à la structure :samp:`do {expression} while {condition}`; cette dernière peut être remplacée par: .. code-block:: python while True: # calcul de la condition d'arrêt if condition: break .. rubric:: Exercices: :ref:`integ`, :ref:`fizz`, :ref:`pgcd` Les chaînes de caractères ========================= .. index:: pair: itérables; str pair: itérables; len Indexation ---------- Les chaînes de caractères sont des objets *itérables* -- c.-à-d. constitués d'éléments (ici les caractères) sur lesquels il est possible de « boucler » (p.ex. avec `for`) -- et *immuables* -- c.-à-d. dont les éléments individuels ne peuvent pas être modifiés intrinsèquement. .. Note:: Comme en C[++], l'indexation en Python commence à 0: le 1er élément d'une liste est l'élément n°0, le 2e est le n°1, etc. Les *n* éléments d'une liste sont donc indexés de 0 à *n-1*. >>> alpha = 'abcdefghijklmnopqrstuvwxyz' >>> len(alpha) 26 >>> alpha[0] # 1er élément (l'indexation commence à 0) 'a' >>> alpha[-1] # = alpha[26-1=25], dernier élément (-2: avant-dernier, etc.) 'z' Sous-liste (*slice*) -------------------- .. index:: pair: itérables; slice Des portions d'une chaîne peuvent être extraites en utilisant des :class:`slice` (« tranches »), de notation générique :samp:`{[start=0]}:{[stop=len]}{[:step=1]}`. P.ex. >>> alpha[3:7] # De l'élément n°3 (inclus) au n°7 (exclu), soit 7-3=4 éléments 'defg' >>> alpha[:3] # Du n°0 (défaut) au n°3 (exclu), soit 3 éléments 'abc' >>> alpha[-3:] # Du n°26-3=23 (inclus) au dernier inclus (défaut) 'xyz' >>> alpha[3:9:2] # Du n°3 (inclus) au n°9 (exclu), tous les 2 éléments 'dfh' >>> alpha[::5] # Du 1er au dernier élément (défauts), tous les 5 éléments 'afkpuz' Méthodes -------- .. index:: dir Comme la plupart des objets en Python, les chaînes de caractères disposent de :ref:`nombreuses fonctionnalités ` -- appelées « méthodes » en :abbr:`POO (Programmation Orientée Objet)` -- facilitant leur manipulation: >>> enfant, peluche = "Calvin", 'Hobbes' # Affectations mutiples >>> titre = enfant + ' et ' + peluche; titre # +: Concaténation de chaînes 'Calvin et Hobbes' >>> titre.replace('et', '&') # Remplacement de sous-chaînes (→ nouvelle chaîne) 'Calvin & Hobbes' >>> titre # titre est immuable et reste inchangé 'Calvin et Hobbes' >>> ' & '.join(titre.split(' et ')) # Découpage (split) et jonction (join) 'Calvin & Hobbes' >>> 'Hobbes' in titre # in: Test d'inclusion True >>> titre.find("Hobbes") # str.find: Recherche de sous-chaîne 10 >>> titre.center(30, '-') '-------Calvin et Hobbes-------' >>> dir(str) # Liste toutes les méthodes des chaînes ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill'] .. _print: Formatage --------- .. index:: print f-string Le système de formatage permet un contrôle précis de la conversion de variables en chaînes de caractères. Après quelques tergiversations historiques [#format]_, le système de choix est dorénavant (Python 3.6+) celui de la chaîne formatée (:term:`f-string`), qui interprète directement les éléments du type `{var[:format]}` dans une chaîne: >>> nom, age = 'calvin', 6 >>> f"{nom} a {age} ans." # Interpolation simple 'calvin a 6 ans.' >>> f"L'année prochaine, {nom.capitalize()} aura {age+1} ans." # Interprétation "L'année prochaine, Calvin aura 7 ans." >>> f"{nom=}, {nom.capitalize()=}, {age=}, {age+1=}" # Mode debug (3.8+) "nom='calvin', nom.capitalize()='Calvin', age=6, age+1=7" Le formatage précis des chaînes (taille, justification, nombre de chiffres significatif, signe, etc.) peut se faire à l'aide du :ref:`mini-language de formatage `: >>> f"{nom:10s}, {nom:->10s}, {nom:!<10s}, {nom:=^10s}" # Formatage de chaîne, ... "calvin , ----calvin, calvin!!!!, ==calvin==" >>> f"{3} fois {7:03d} font {3*7:_^+5d}" # ..., d'entier, ... '3 fois 007 font _+21_' >>> pi = 3.1415926535897931 >>> f"{pi=}, {pi:f}, {pi:·<+9.3f}, {pi:.3g}, {pi*1e3:.2f}, {pi*1e3:.3g}" # ..., de réel. 'pi=3.141592653589793, 3.141593, +3.142···, 3.14, 3141.59, 3.14e+03' :func:`print` affiche à l'écran (plus spécifiquement la sortie standard par défaut) la conversion d'une variable en chaîne de caractères: >>> print("Calvin and Hobbes\nScientific progress goes 'boink'!") Calvin and Hobbes Scientific progress goes 'boink'! >>> z = 1 + 2j; print(z) # = print(str(z)) (1+2j) .. rubric:: Exercice: :ref:`tables` Objets itérables ================ .. index:: itérables pair: itérables; en compréhension pair: itérables; enumerate pair: itérables; zip Les chaînes de caractères, listes, tuples et dictionnaires sont les objets itérables de base en Python. Les listes et dictionnaires sont *modifiables* (« *mutables* ») -- leurs éléments constitutifs peuvent être changés à la volée -- tandis que chaînes de caractères et les tuples sont *immuables*. - Accès indexé: conforme à celui des chaînes de caractères >>> l = list(range(1, 10, 2)); l # De 1 (inclus) à 10 (exclu) par pas de 2 [1, 3, 5, 7, 9] >>> len(l) # Nb d'éléments dans la liste (i varie de 0 à 4) 5 >>> l[0], l[-2] # 1er et avant-dernier élément (l'indexation commence à 0) (1, 7) >>> l[5] # Erreur: indice hors-bornes IndexError: list index out of range >>> d = dict(a=1, b=2) # Création du dictionnaire {'a':1, 'b':2} >>> d['a'] # Accès à une entrée via sa clé 1 >>> d['c'] # Erreur: clé inexistante! KeyError: 'c' >>> d['c'] = 3; d # Ajout d'une clé et sa valeur {'a': 1, 'b': 2, 'c': 3} .. Note:: Depuis Python 3.7 (en pratique depuis 3.6), un dictionnaire est *ordonné*, i.e., conserve l'ordre d'insertion (ce n'était pas le cas dans l'implémentation historique). - Sous-listes (*slices*): >>> l[1:-1] # Du 2e ('1') *inclus* au dernier ('-1') *exclu* [3, 5, 7] >>> l[1:-1:2] # Idem, tous les 2 éléments [3, 7] >>> l[::2] # Tous les 2 éléments (*start=0* et *stop=len* par défaut) [1, 5, 9] - Modification d'éléments d'une liste (chaînes et tuples sont **immuables**): >>> l[0] = 'a'; l # Remplacement du 1er élément ['a', 3, 5, 7, 9] >>> l[1::2] = ['x', 'y']; l # Remplacement d'éléments par *slices* ['a', 'x', 5, 'y', 9] >>> l + [1, 2]; l # Concaténation (l reste inchangé) ['a', 'x', 5, 'y', 9, 1, 2] ['a', 'x', 5, 'y', 9] >>> l += [1, 2]; l # Concaténation sur place (l est modifié) ['a', 'x', 5, 'y', 9, 1, 2] >>> l.append('z'); l # Ajout d'un élément en fin de liste ['a', 'x', 5, 'y', 9, 1, 2, 'z'] >>> l.extend([-1, -2]); l # Extension par une liste ['a', 'x', 5, 'y', 9, 1, 2, 'z', -1, -2] >>> del l[-6:]; l # Efface les 6 derniers éléments de la liste ['a', 'x', 5, 'y'] .. Attention:: à la modification des objets *mutables*: >>> l = [0, 1, 2] >>> m = l; m # m est un *alias* de la liste l: c'est le même objet [0, 1, 2] >>> id(l); id(m); m is l 171573452 # id({obj}) retourne le n° d'identification en mémoire 171573452 # m et l ont le même id: True # ils correspondent donc bien au même objet en mémoire >>> l[0] = 'a'; m # puisque l a été modifiée, il en est de même de m ['a', 1, 2] >>> m = l[:] # copie tous les elts de l dans une *nouvelle* liste m (clonage) >>> id(l); id(m); m is l 171573452 171161228 # m a un id différent de l: il s'agit de 2 objets distincts False # (contenant éventuellement la même chose!) >>> del l[-1]; m # les éléments de m n'ont pas été modifiés ['a', 1, 2] - Liste en compréhension :samp:`[ {expression} for {element} in {iterable} ]`: elle permet la construction d'une liste à la volée, plus lisible et plus rapide que la méthode utilisant une boucle explicite >>> l = [] >>> for i in range(5): ... l.append(i**2) # Méthode traditionnelle: construction élément par élément >>> [ i**2 for i in range(5) ] # Compréhension: carré des entiers de 0 à 4 [0, 1, 4, 9, 16] >>> [ i for i in range(10) if (i%3 == 0) ] # Compréhension conditionnelle (multiples de 3) [0, 3, 6, 9] >>> [ 10*i+j for i in range(3) for j in range(4) ] # Double compréhension [0, 1, 2, 3, 10, 11, 12, 13, 20, 21, 22, 23] >>> [ [ 10*i+j for i in range(3) ] for j in range(4) ] # Compréhensions imbriquées [[0, 10, 20], [1, 11, 21], [2, 12, 22], [3, 13, 23]] .. Attention:: Ne pas inclure de calculs inutiles dans la liste en compréhension! >>> [ calcul(x)**i for i in range(100) ] # Argh, `calcul(x)` sera calculé 100 fois! >>> y = calcul(x); [ y**i for i in range(100) ] # Bien mieux! - Dictionnaire en compréhension :samp:`{ {key}: {value} for {element} in {iterable} }`: de manière similaire, elle permet la construction d'un dictionnaire à la volée >>> { i: bin(i) for i in range(5) } # Dictionnaire {i: représentation binaire} {0: '0b0', 1: '0b1', 2: '0b10', 3: '0b11', 4: '0b100'} - Utilitaires sur les itérables: >>> humans = ['Calvin', 'Wallace', 'Boule'] >>> for i in range(len(humans)): # Boucle sur les indices de humans ... print(i, humans[i]) # Accès explicite, pas pythonique :-( 0 Calvin 1 Wallace 2 Boule >>> for i, name in enumerate(humans): # Boucle sur (indice, valeur) de humans ... print(i, name) # Pythonique :-D 0 Calvin 1 Wallace 2 Boule >>> animals = ['Hobbes', 'Gromit', 'Bill'] >>> for boy, dog in zip(humans, animals): # Boucle simultanée sur 2 listes (ou +) ... print(boy, 'et', dog) Calvin et Hobbes Wallace et Gromit Boule et Bill >>> sorted(zip(humans, animals)) # Tri, ici sur le 1er élément de chaque tuple de la liste [('Boule', 'Bill'), ('Calvin', 'Hobbes'), ('Wallace', 'Gromit')] .. rubric:: Exercices: :ref:`crible`, :ref:`carre` .. _fonctions: Fonctions ========= Une fonction est un regroupement d'instructions impératives -- assignations, branchements, boucles, etc. -- s'appliquant sur des arguments d'entrée. C'est le concept central de la programmation *impérative*. .. index:: def, args, kwargs `def` permet de définir une fonction: :samp:`def {fonction}({arg1}, {arg2}, ..., {option1}={valeur1}, {option2}={valeur2}, ...):`. Les « *args* » sont des arguments nécessaires (c.-à-d. obligatoires) caractérisés par leur position (*positional arguments*), tandis que les « *kwargs* » -- arguments de type :samp:`{option}={valeur}` caractérisés par leur mot-clé (*keyword arguments*) -- sont optionnels, puisqu'ils possèdent une valeur par défaut. Si la fonction doit retourner une valeur, celle-ci est spécifiée par le mot-clé :keyword:`return`. .. rubric:: Exemples: .. code-block:: python :linenos: def temp_f2c(tf): """ Convertit une température en d° Fahrenheit `tf` en d° Celsius. Exemple: >>> temp_f2c(104) 40.0 """ tc = (tf - 32.)/1.8 # Fahrenheit → Celsius return tc Dans la définition d'une fonction, la première chaîne de caractères (appelé *docstring*) servira de documentation pour la fonction, accessible de l'interpréteur via p.ex. `help(temp_f2c)`, ou `temp_f2c?` sous `ipython`. Elle se doit d'être tout à la fois pertinente, concise *et* complète. Elle peut également inclure des exemples d'utilisation (*doctests*, voir :ref:`TDD`). .. literalinclude:: mean_power.py :pyobject: mean_power :linenos: Il faut noter plusieurs choses importantes: - Python est un langage à typage *dynamique*, p.ex., le type des arguments d'une fonction n'est pas fixé *a priori*. Dans l'exemple précédent, `alist` peut être une `list`, un `tuple` ou tout autre itérable contenant des éléments pour lesquels les opérations effectuées -- somme, exponentiation, division par un entier -- ont été préalablement définies (p.ex. des entiers, des complexes, des matrices, etc.): c'est ce que l'on appelle le :term:`duck-typing` [#duck]_, favorisant le polymorphisme des fonctions; - le typage est *fort*, c.-à-d. que le type d'une variable ne peut pas changer à la volée. Ainsi, `"abra" + "cadabra"` a un sens (concaténation de chaînes), mais pas `1 + "2"` ou `3 + "cochons"` (entier + chaîne); - la définition d'une fonction se fait dans un « espace parallèle » où les variables ont une portée (*scope*) locale [#scope]_. Ainsi, la variable `s` définie *dans* la fonction `mean_power` n'interfère pas avec le « monde extérieur » ; inversement, la définition de `mean_power` ne connaît *a priori* rien d'autre que les variables explicitement définies dans la liste des arguments ou localement. Pour les noms de variables, fonctions, etc. utilisez de préférence des caractères purement ASCII [#fnunicode]_ (`a-zA-Z0-9_`); de manière générale, favorisez plutôt la langue anglaise (variables, commentaires, affichages). .. rubric:: Exercice: :ref:`syracuse` Bibliothèques et scripts ======================== Bibliothèques externes ---------------------- .. index:: import Une bibliothèque est un ensemble d'instructions fournissant des fonctionnalités supplémentaires -- p.ex. des constantes ou fonctions prédéfinies -- au langage Python. Ainsi, la bibliothèque :mod:`math` définit les fonctions et constantes mathématiques usuelles (`sqrt()`, `pi`, etc.) Une bibliothèque est « importée » avec la commande :samp:`import {module}`. Les fonctionnalités supplémentaires sont alors accessibles dans l'*espace de noms* :samp:`{module}` via :samp:`{module}.{fonction}`: >>> sqrt(2) # sqrt n'est pas une fonction standard de python NameError: name 'sqrt' is not defined >>> import math # Importe le module 'math' >>> dir(math) # Liste les fonctionnalités de 'math' ['__doc__', '__name__', '__package__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'copysign', 'cos', 'cosh', 'degrees', 'e', 'exp', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'hypot', 'isinf', 'isnan', 'ldexp', 'log', 'log10', 'log1p', 'modf', 'pi', 'pow', 'radians', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'trunc'] >>> math.sqrt(math.pi) # Les fonctionnalités sont disponibles sous 'math' 1.7724538509055159 >>> import math as M # Importe 'math' avec l'alias 'M' >>> M.sqrt(M.pi) 1.7724538509055159 >>> from math import sqrt, pi # Importe uniquement 'sqrt' et 'pi' dans l'espace courant >>> sqrt(pi) 1.7724538509055159 .. Danger:: Il est possible d'importer toutes les fonctionnalités d'une bibliothèque dans l'espace de noms courant: >>> from math import * # Argh! Pas pythonique :-( >>> sqrt(pi) 1.7724538509055159 Cette pratique est cependant fortement *déconseillée* du fait des confusions dans les espaces de noms qu'elle peut entraîner: >>> from cmath import * >>> sqrt(-1) # Quel sqrt: le réel (de math) ou le complexe (de cmath)? Nous verrons par la suite quelques exemples de bibliothèques de la :ref:`standard`, ainsi que des :ref:`science` orientées analyse numérique. .. rubric:: Exercice: :ref:`koch` Bibliothèques personnelles et scripts ------------------------------------- Vous pouvez définir vos propres bibliothèques en regroupant les fonctionnalités au sein d'un même fichier :samp:`{monfichier}.py` (il s'agit alors d'un *module*). * Si ce fichier est importé (p.ex. `import monfichier`), il agira comme une bibliothèque; * si ce fichier est exécuté -- p.ex. `python ./monfichier.py` -- il agira comme un *script*. .. Attention:: Toutes les instructions d'un module qui ne sont pas encapsulées dans le bloc :obj:`python:__main__` (voir plus bas) sont interprétées et exécutées lors de l'`import` du module. Elles doivent donc en général se limiter à la définition de variables, de fonctions et de classes (en particulier, éviter les affichages ou les calculs longs). Un code Python peut donc être: - un *module*, s'il n'inclut que des définitions mais pas d'instruction exécutable en dehors d'un éventuel bloc `__main__` [#dunder]_; - un *exécutable*, s'il inclut un bloc `__main__` ou des instructions exécutables; - ou les deux à la fois. .. rubric:: Exemple: Le code :ref:`mean_power.py ` peut être importé comme une bibliothèque (p.ex. `import mean_power`) dans un autre code Python, ou bien être exécuté depuis la ligne de commande (p.ex. `python mean_power.py`), auquel cas le bloc `__main__` sera exécuté. - `#!` (:wen:`Hash-bang `): la première ligne d'un script défini l'interpréteur à utiliser [#shebang]_:: #!/usr/bin/env python3 - `"""doc"""`: la chaîne de documentation de la bibliothèque (*docstring*, :pep:`257`), qui sera utilisée comme aide en ligne du module (`help(mean_power)`), doit être la *1re* instruction du script. - `if __name__ == '__main__':` permet de séparer le bloc `__main__` (c.-à-d. le corps du programme, à exécuter lors d'une utilisation en script) des définitions de fonctions et classes, permettant une utilisation en module. Exceptions ========== .. index:: pair: exceptions; try: ... except: ... Comme nous avons pu le voir, l'interpréteur Python, lorsqu'il rencontre une erreur dans l'exécution d'une instruction, génère une :ref:`Exception `, de nature différente selon la nature de l'erreur: :exc:`IndexError` (p.ex. indice de liste invalide), :exc:`KeyError` (p.ex. clé de dictionnaire invalide), :exc:`NameError` (p.ex. variable ou fonction inconnue), :exc:`IOError` (p.ex. fichier inconnu), :exc:`TypeError`, :exc:`ValueError`, :exc:`AttributeError`, :exc:`NotImplementedError`, :exc:`KeyboardInterrupt` (p.ex. :kbd:`Ctrl-c`), etc. La nature exacte de l'exception est précisée dans la documentation de la fonction la générant, ou déterminée empiriquement: >>> int("toto") # la valeur de la chaîne ne permet pas la conversion en entier ValueError: invalid literal for int() with base 10: 'toto' >>> int([1,]) # le type list ne peut pas être converti en entier TypeError: int() argument must be a string, a bytes-like object or a number, not 'list' La levée d'une erreur n'est cependant pas nécessairement fatale, puisque Python dispose d'un mécanisme avancé de *gestion des erreurs*. Gestion des exceptions ---------------------- Il est d'usage en Python d'utiliser la philosophie :abbr:`EAFP (Easier to Ask for Forgiveness than Permission)` [#lbyl]_: plutôt que de tester explicitement toutes les conditions de validité d'une instruction, on « tente sa chance » d'abord, quitte à gérer les erreurs *a posteriori*. Cette gestion des exceptions se fait par la construction :ref:`try/except `:: try: # instructions pouvant lever une exception ... except Exception1: # Type d'exception interceptée # instructions à exécuter en cas d'exception de ce(s) type(s) ... [except (Exception2, Exception3): # Autres exceptions interceptées # instructions à exécuter en cas d'exception de ce(s) type(s) ...] [else: # instructions à exécuter si *aucune* exception ...] [finally: # instructions à exécuter dans tous les cas (avant de sortir du try/except) ...] Dans l'élaboration d'un programme, gérez explicitement les erreurs que vous auriez pu tester *a priori* et pour lesquels il existe une solution de repli, et laissez passer les autres (ce qui provoquera éventuellement l'interruption légitime du programme). .. rubric:: Exemples: .. code-block:: python try: y = math.sqrt(x) # ValueError si x<0, TypeError si x n'est pas numérique except ValueError: print(f"Avertissement: {x=} < 0, on impose y=-1") y = -1 # Note: l'exception TypeError n'est pas interceptée, parce qu'aucune # solution de repli n'est prévue dans ce cas (x devrait être numérique, # le pb est plus profond!) .. _input: .. code-block:: python :linenos: def lireEntier(): while True: # Boucle infinie chaine = input("Entrez un entier: ") # Lecture du clavier → str try: # int() génère ValueError si la chaîne n'est pas coercible en entier return int(chaine) # Seule sortie possible de la boucle infinie except ValueError: # Gestion de l'exception ValueError print(f"{chaine!r} n'est pas un entier") >>> lireEntier() Entrez un entier: toto 'toto' n'est pas un entier Entrez un entier: 3,4 '3,4' n'est pas un entier Entrez un entier: 4 4 .. Danger:: Évitez à tout prix les :keyword:`except` *nus*, c.-à-d. ne spécifiant pas la ou les exceptions à gérer, car ils intercepteraient alors *toutes* les exceptions, y compris celles que vous n'aviez pas prévues! Trouvez l'erreur dans le code suivant:: y = 2 try: x = z # Copie y dans x print("Tout va bien") except: # Intercepte tout, y compris les erreurs légitimes! print("Rien ne va plus") .. index:: pair: exceptions; raise Genèse des exceptions --------------------- Vos procédures doivent également générer des exceptions (*documentées*) -- avec l'instruction :samp:`raise {Exception()}` -- si elles ne peuvent conclure leur action, à charge pour la procédure appelante de les gérer si besoin: .. code-block:: python :linenos: def diff_sqr(x, y): """ Return x**2 - y**2 for x >= y, raise ValueError otherwise. Exemples: >>> diff_sqr(5, 3) 16 >>> diff_sqr(3, 5) Traceback (most recent call last): ... ValueError: x=3 < y=5 """ if x < y: raise ValueError(f"{x=} < {y=}") return x**2 - y**2 .. index:: pair: exceptions; assert Avant de se lancer dans un calcul long et complexe, on peut vouloir tester la validité de certaines hypothèses fondamentales, soit par une structure `if ... raise`, ou plus facilement à l'aide d':keyword:`assert` (qui, si l'hypothèse n'est pas vérifiée, génère une :exc:`AssertionError`): .. code-block:: python :linenos: def diff_sqr(x, y): """ Returns x**2 - y**2 for x >= y, AssertionError otherwise. """ assert x >= y, f"{x=} < {y=}" # Test et msg d'erreur return x**2 - y**2 .. Note:: La règle générale à retenir concernant la gestion des erreurs: **Fail early, fail often, fail better!** .. rubric:: Exercice: :ref:`pm` Programmation Orientée Objet ============================ .. index:: class La :wfr:`Programmation Orientée Objet ` (POO) est une façon de programmer qui consiste à organiser le code autour d'*objets*. Chaque objet regroupe à la fois des *données* (ce qu'il contient) et des *comportements* (ce qu'il sait faire) spécifiques qui lui sont intimement liés. Cette approche permet d'avoir un code plus clair, plus modulable et plus réutilisable, ce qui facilite la maintenance et les modifications. Les quatre grands principes de la POO (pas nécessairement tous utilisés simultanément) sont: 1. **Encapsulation** Les données et les méthodes d'un objet sont regroupées ensemble. Cela cache les détails internes (l'implémentation technique) et ne montre que ce qui est nécessaire à l'utilisateur extérieur. 2. **Abstraction** Il est possible de créer des classes *abstraites* (ou *interfaces*) qui décrivent ce qui est attendu d'un objet en terme de données et comportement, mais sans préciser comment. Cela rend le code plus flexible et plus facile à faire évoluer. 3. **Héritage** Une classe peut être créée à partir d'une autre, afin de la compléter. Elle *hérite* alors des propriétés et des comportements de sa classe parente, ce qui permet de réutiliser du code et d'ajouter des fonctionnalités spécifiques. 4. **Polymorphisme** Un même code peut fonctionner simultanément avec différents types d'objets. Ainsi, différentes classes (éventuellement parentes) peuvent fournir des interfaces similaires mais se comportant différemment selon le type d'objet. Au concept d'objet sont liées les notions de: - **Classe:** il s'agit d'un *modèle* à partir duquel sont créés les objets, et dans lequel sont définis ses propriétés usuelles. P.ex. la classe `Animal` peut représenter un animal caractérisé par sa masse, et disposant de fonctionnalités propres, p.ex. grossir; - **Instanciation:** c'est le fait générer un objet concret (appelée *instance*) à partir d'un modèle (la classe). P.ex. `vache = Animal(500.)` crée une instance *vache* à partir de la classe `Animal` et d'une masse (`float`): - **Attributs:** ce sont les variables internes décrivant l'état de l'objet. P.ex., `vache.masse` donne la masse de l'`Animal` *vache*; - **Méthodes:** ce sont les fonctions *internes* à l'objet, s'appliquant en premier lieu sur l'objet lui-même (`self`), décrivant les capacités de l'objet et utilisant ou modifiant ses attributs. P.ex. `vache.grossit(10)` modifie la `masse` de l'`Animal` *vache*; .. Attention:: Toutes les méthodes d'une classe doivent au moins prendre `self` -- représentant l'instance même de l'objet -- comme premier argument. - **Surcharge d'opérateurs:** cette technique permet de *redéfinir* les opérateurs et fonctions usuelles (p.ex. `+`, `abs()`, `str()`, etc.), pour simplifier l'écriture d'opérations sur les objets. Ainsi, pour que les opérations du genre `animal1 < animal2` aient un sens (p.ex. en comparant les masses), on peut *surcharcher* les opérateurs de comparaison (`<`, `>=`, etc.) dans la classe `Animal`. - **Héritage de classe:** il s'agit de définir une nouvelle classe à partir d'une (ou plusieurs) classe(s) parente(s). La classe enfant *hérite* des attributs et méthodes de sa (ses) parente(s), et ces fonctionnalités peuvent alors être modifiées ou complétées. P.ex. la classe `AnimalFeroce` hérite de la classe `Animal` (elle partage la notion de masse), et lui ajoute des méthodes propres à la notion d'animal féroce (p.ex. dévorer un autre animal). .. rubric:: Exemple de définition de classe .. literalinclude:: animal.py :pyobject: Animal :linenos: .. rubric:: Exemple d'héritage de classe .. literalinclude:: animal.py :pyobject: AnimalFeroce :linenos: .. literalinclude:: animal.py :pyobject: AnimalGentil :linenos: .. Note:: Il est traditionnel d'écrire les noms de classes en *CamelCase* (`AnimalGentil`), et les noms d'instances de classe (les variables) en minuscules (`vache`). .. rubric:: Exemples :ref:`animal`, :ref:`circonscrit` .. rubric:: Études de cas - :class:`turtle.Vec2D` - :class:`fractions.Fraction` .. rubric:: Exercices: :ref:`animaux`, :ref:`life` .. Espaces de noms et portée (*scope*) .. =================================== Entrées-sorties =============== Intéractif ---------- .. index:: print, input Comme nous avons pu le voir précédemment (print_ et input_), l'affichage à l'écran (sur la sortie standard) se fait par :func:`print` , la lecture du clavier (entrée standard) par :func:`input`. Si nécessaire, l'affichage dans l'erreur standard (*stderr*) est possible: >>> import sys >>> print("Erreur!", file=sys.stderr) Fichiers texte -------------- .. index:: open, file La gestion des fichiers (lecture et écriture) se fait à partir de la fonction :func:`open` retournant un objet de type :term:`file object`: .. code-block:: python :linenos: # ========== ÉCRITURE ========== outfile = open("carres.dat", 'w') # Ouverture d'un fichier texte en écriture for i in range(1, 10): outfile.write(f"{i} {i**2}\n") # Noter la présence du '\n' (non-automatique) outfile.close() # Fermeture du fichier, absolument **NÉCESSAIRE** en écriture! # ========== LECTURE ========== infile = open("carres.dat") # Ouverture du fichier texte "carres.dat" en lecture for line in infile: # Boucle sur les lignes du fichier if line.strip().startswith('#'): # Ne pas considérer les lignes "commentées" continue try: # Essayons de lire 2 entiers sur cette ligne x, x2 = [ int(tok) for tok in line.split() ] # ValueError possible except ValueError: # Gestion de l'erreur de conversion print(f"Cannot decipher line {line!r}.") continue print(f"{x}**3 = {x**3}") infile.close() # Fermeture du fichier, moins critique en lecture Il est préconisé d'utiliser un *gestionnaire de contexte* (:keyword:`with`, sur lequel je ne reviendrai pas), qui se charge en particulier de la fermeture automatique du fichier:: with open(...) as thefile: ... .. rubric:: Notes de bas de page .. [#braces] ou `from __future__ import braces` :-) .. [#format] Utilisation native du `%` avec la grammaire C :ref:`printf `, et plus récemment de la méthode de formatage des chaînes :meth:`python:str.format`; ces deux options sont encore valables et largement utilisées. Voir :pypi:`flynt` pour un outil de conversion automatique des anciennes chaînes en *f-strings*. .. [#duck] *If it looks like a duck and quacks like a duck, it must be a duck.* .. [#match] Voir cependant la construction `match/case` de Python 3.10 (:pep:`636`). .. [#scope] La notion de « portée » est plus complexe, je simplifie... .. [#dunder] Parfois prononcé *dunder main* (*dunder* désigne le double `_`). .. [#shebang] Il s'agit d'une fonctionnalité des *shells* d'Unix, pas spécifique à Python. .. [#fnunicode] En fait, Python 3 gère nativement les caractères :doc:`Unicode `: >>> α, β = 3, 4 >>> print("α² + β² =", α**2 + β**2) α² + β² = 25 .. [#lbyl] Par opposition au :abbr:`LBYL (Look Before You Leap)` du C/C++, basé sur une série *exhaustive* de tests *a priori*. .. |DANGER| unicode:: 0x2620 .. ☠ = SKULL AND CROSSBONES .. |:-)| unicode:: 0x263a .. ☺ = WHITE SMILING FACE .. |fr| image:: ../_static/france_flag_icon.png :alt: Fr .. |en| image:: ../_static/uk_flag_icon.png :alt: En .. ①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳ .. ↑↓→≡≠∈ℕℤℝℂ .. ☛☠☹☺⚡⚠