.. _cours:
Initiation à Python
###################
.. contents:: Table des matières
:local:
..
1 Introduction
1.1 Installation
1.2 Notions d'Unix
1.3 L'invite de commande
2 Types de base
3 Structures de programmation
4 Les chaînes de caractères
4.1 Indexation
4.2 Sous-liste (*slice*)
4.3 Méthodes
4.4 Formatage
5 Objets itérables
6 Fonctions
7 Bibliothèques et scripts
7.1 Bibliothèques externes
7.2 Bibliothèques personnelles et scripts
8 Exceptions
9 Classes
10 Entrées-sorties
10.1 Intéractif
10.2 Fichiers
.. _td1:
Introduction
============
Installation
------------
Cette introduction repose essentiellement sur les outils suivants:
- `Python 3.6+ `_ (inclus
l'interpréteur de base et la bibliothèque standard);
- les bibliothèques scientifiques `Numpy et Scipy
`_;
- la bibliothèque graphique :doc:`Matplotlib `;
- un interpréteur évolué, p.ex. `ipython `_;
- un `éditeur de texte évolué
`_, p.ex. `emacs
`_, `vi `_, `gedit
`_ ou `Atom `_.
Ces logiciels peuvent être installés indépendamment, de préférence sous Linux
(p.ex. `Ubuntu `_ ou votre `distribution préférée
`_), ou sous
Windows ou MacOS. Il existe également des distributions « clés en main », p.ex.
`Conda `_ (multi-plateforme).
Notions d'Unix
--------------
Les concepts suivants sont supposés connus:
* ligne de commande: éxécutables et options;
* arborescence: chemin relatif (`[./]...`) et absolu (`/...`),
navigation (:command:`cd`);
* gestion des fichiers (:command:`ls`, :command:`rm`, :command:`mv`)
et répertoires (:command:`mkdir`);
* gestion des éxécutables: :envvar:`$PATH`, :command:`chmod +x`;
* gestion des processus: `&`, :kbd:`Control-c`, :kbd:`Control-z` +
:command:`bg`;
* variables d'environnement: :command:`export`, :file:`.bashrc`.
.. rubric:: Liens:
* `Quelques notions et commandes d'UNIX
`_
|fr|
* `Introduction to Unix Study Guide
`_
L'invite de commande
--------------------
Il existe principalement deux interpréteurs intéractifs de commandes
Python:
.. index:: interpréteur; python
* :program:`python`: :doc:`interpréteur de base `::
$ python3
Python 3.6.11 (default, Jun 29 2020, 05:15:03)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
- :kbd:`Control-d` pour sortir;
- :samp:`help({commande})` pour obtenir l'aide d'une commande;
- *a priori*, pas d'historique des commandes ni de complétion
automatique.
L'interpréteur de base permet également d'interpréter un « script »,
c.-à-d. un ensemble de commandes regroupées dans un fichier texte
(généralement avec une extension `.py`): :samp:`python
{mon_script.py}`
.. index:: interpréteur; ipython
* :program:`ipython`: :rtfd:`interpréteur évolué ` (avec historique et
complétion automatique des commandes)::
$ ipython
Python 3.6.11 (default, Jun 29 2020, 05:15:03)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.16.1 -- An enhanced Interactive Python. Type '?' for help.
In [1]:
- :kbd:`Control-d` pour sortir;
- :kbd:`Tab` pour la complétion automatique;
- :kbd:`Haut` et :kbd:`Bas` pour le rappel des commandes;
- :kbd:`%quickref` pour les commandes spécifiques à :program:`ipyhon`,
notamment `object?` pour une aide sur un objet, `object??`
pour une aide plus complète (au niveau source);
- :kbd:`%magic` pour la liste des commandes *magiques*, dont
* :samp:`%run {mon_script.py}` pour éxecuter un script *dans*
l'interpréteur,
* `%debug` pour lancer le mode débogage intéractif *post-mortem*,
* `%cpaste` pour coller et éxecuter un code préformaté.
.. rubric:: Liens:
- `Tutorial
`_
- `IPython Tips & Tricks
`_
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 + 2 == 4`), toute valeur ou
objet non nul (et donc s'évaluant par défaut à `True` *sauf exception*)
- Tests logiques: `==`, `!=`, `>`, `>=`, etc.
.. Attention:: Ne pas confondre « `=` » (affectation d'une variable)
et « `==` » (test logique d'égalité).
- Opérateurs logiques: :keyword:`and`, :keyword:`or`, :keyword:`not`
>>> (5 >= 6) or (not 3 > 4)
True
>>> x = 3; 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.
.. 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
--------
.. 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 "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 "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
.. ①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳
.. ↑↓→≡≠∈ℕℤℝℂ
.. ☛☠☹☺⚡⚠