.. _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 8 Classes 9 Entrées-sorties 9.1 Intéractif 9.2 Fichiers texte .. _td1: Types de base ============= .. index:: None - `None` (rien) .. index:: pair: itérables; str - **Chaînes de caractères**: :class:`str` - Entre (simples ou triples) apostrophes `'` ou guillemets `"`: `'Calvin'`, `"Calvin'n'Hobbes"`, `'''Deux\\\nlignes'''`, `"""'Pourquoi?' demanda-t-il."""` - Conversion: `str(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**: - *Booléens* :class:`bool` (vrai/faux): `True`, `False`, `bool(3)` - *Entiers* :class:`int` (pas de valeur limite explicite, correspond *au moins* au `long` du C): `-2`, `int(2.1)`, `int("4")` - *Réels* :class:`float` (entre ±1.7e±308, correspond au `double` du C): `2.`, `3.5e-6`, `float(3)` - *Complexes* :class:`complex`: `1+2j` (sans espace), `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**: - *Listes* :class:`list`: `['a', 3, [1, 2], 'a']` - *Listes immuables* :class:`tuple`: `(2, 3.1, 'a', [])` (selon les conditions d'utilisation, les parenthèses ne sont pas toujours nécessaires) - *Listes à clés* :class:`dict`: `{'a':1, 'b':[1, 2], 3:'c'}` - *Ensembles* non ordonnés d'éléments uniques :class:`set`: `{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)) # 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, :func:`range` n'est plus un constructeur de liste, mais un *itérateur*, qui doit être converti en liste explicitement (équivalent à `xrange` de Python 2): >>> range(3) # Itérateur range(0, 3) >>> list(range(3)) # 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 =========================== - Les blocs sont définis par l'**indentation** (en général par pas de quatre espaces) [#braces]_. .. Warning:: É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. .. 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 - :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 assez 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, ni à la structure :samp:`do ... 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` .. _td2: 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 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[:fmt]}"` 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." Le formatage des chaînes hérite de la :ref:`grammaire standard ` du C: >>> pi = 3.1415926535897931 >>> f"{pi:f}, {pi:+06.2f}, {pi*1e9:f}, {pi*1e9:.3g}" # Options de formatage '3.141593, +03.14, 3141592653.589793, 3.14e+09' :func:`print` affiche à l'écran (plus spécifiquement la sortie standard) 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'! >>> print(f"{3:2d} fois {4:2d} font {3*4:2d}") # Formatage et affichage 3 fois 4 font 12 .. rubric:: Exercice: :ref:`tables` Objets itérables ================ .. index:: itérables 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, 'c': 3, 'b': 2} >>> # Noter qu'un dictionnaire N'est PAS ordonné! - 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 de tous les éléments 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: elle permet la construction d'une liste à la volée >>> [ i**2 for i in range(5) ] # Carré de tous les éléments de [0, ..., 4] [0, 1, 4, 9, 16] >>> [ 2*i for i in range(10) if (i%3 != 0) ] # Compréhension conditionnelle [2, 4, 8, 10, 14, 16] >>> [ 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]] >>> { i: i**2 for i in range(1, 5) } # Dictionnaire en compréhension {1: 1, 2: 4, 3: 9, 4: 16} .. - (générateurs) - 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), tandis que les « *kwargs* » -- arguments de type :samp:`{option}={valeur}` -- 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 charactè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 (ou module) est un code fournissant des fonctionnalités supplémentaires -- p.ex. des fonctions prédéfinies -- à Python. Ainsi, le module :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 tout 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' dans l'espace '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 .. Warning:: 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 ou le complexe? Nous verrons par la suite quelques exemples de modules 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`. * 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 `__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 `__main__` [#dunder]_; - un exécutable -- s'il inclut un `__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 la partie `__main__` sera exécutée. - `#!` (`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 `__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. .. _td3: Exceptions ========== .. index:: pair: exceptions; try ... except Lorsqu'il rencontre une erreur dans l'exécution d'une instruction, l'interpréteur Python génère (:keyword:`raise`) une erreur (`Exception`), de nature différente selon la nature de l'erreur: :exc:`KeyError`, :exc:`ValueError`, :exc:`AttributeError`, :exc:`NameError`, :exc:`TypeError`, :exc:`IOError`, :exc:`NotImplementedError`, :exc:`KeyboardInterrupt`, etc. La levée d'une erreur n'est cependant pas nécessairement fatale, puisque Python dispose d'un mécanisme de *gestion des erreurs*. 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 `try ... except`. .. _input: .. code-block:: python :linenos: def lireEntier(): while True: chaine = input('Entrez un entier: ') # Lecture du clavier → str try: # La conversion en type entier génère `ValueError` si nécessaire return int(chaine) 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 .. index:: pair: exceptions; raise 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 du programme). .. 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: print("Rien ne va plus") 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={x} < y={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={x} < y={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` Classes ======= .. index:: class Un objet est une entité de programmation, disposant de son propre état interne et de fonctionnalités associées. C'est le concept central de la `Programmation Orientée Objet `_. Au concept d'objet sont liées les notions de: - **Classe:** il s'agit d'un *modèle* d'objet, 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. `grossit()`; - **Instanciation:** c'est le fait générer un objet concret (une *instance*) à partir d'un modèle (une classe). P.ex. `vache = Animal(500.)` crée une instance *vache* à partir de la classe `Animal` et d'une masse (`float`): - **Attributs:** variables internes décrivant l'état de l'objet. P.ex., `vache.masse` donne la masse de l'`Animal` *vache*; - **Méthodes:** fonctions internes, s'appliquant en premier lieu sur l'objet lui-même (`self`), décrivant les capacités de l'objet. 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:** cela permet de redéfinir les opérateurs et fonctions usuels (`+`, `abs()`, `str()`, etc.), pour simplifier l'écriture d'opérations sur les objets. Ainsi, on peut redéfinir les opérateurs de comparaison (`<`, `>=`, etc.) dans la classe `Animal` pour que les opérations du genre `animal1 < animal2` aient un sens (p.ex. en comparant les masses). - **Héritage de classe:** il s'agit de définir une classe à partir d'une (ou plusieurs) classe(s) parente(s). La nouvelle classe *hérite* des attributs et méthodes de sa (ses) parente(s), que l'on peut alors modifier ou compléter. 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 .. index:: input Comme nous avons pu le voir précédemment, l'affichage à l'écran se fait par print_, la lecture du clavier par input_. Fichiers texte -------------- .. index:: open .. index:: 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 du fichier texte "carres.dat" 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 (nécessaire) # ========== 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() ] except ValueError: # Gestion des erreurs print(f"Cannot decipher line {line!r}.") continue print(f"{x}**3 = {x**3}") .. 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. .. [#duck] *If it looks like a duck and quacks like a duck, it must be a duck.* .. [#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 .. ①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳ .. ↑↓→≡≠∈ℕℤℝℂ .. ☛☠☹☺⚡⚠