2. Initiation à Python

2.1. Introduction

2.1.1. Installation

Cette introduction repose sur les outils suivants:

Ces logiciels peuvent être installés indépendamment, de préférence sous Linux (p.ex. Ubuntu, Fedora ou openSUSE), ou sous Windows ou MacOS. Il existe également des distributions « clés en main »:

2.1.2. Notions d’Unix

Les concepts suivants sont supposés connus:

  • ligne de commande: éxécutables et options
  • arborescence: chemin relatif ([./]...) et absolu (/...), navigation (cd)
  • gestion des fichiers (ls, rm, mv) et répertoires (mkdir)
  • gestion des éxécutables: $PATH, chmod +x
  • gestion des processus: &, Control-c, Control-z + bg
  • variables d’environnement: export, .bashrc

Liens:

2.1.3. L’invite de commande

Il existe principalement deux interpréteurs intéractifs de commandes Python:

  • python: l’interpréteur de base:

    $ python
    Python 2.7.6 (default, Mar 22 2014, 22:59:56)
    [GCC 4.8.2] on linux2
    Type "help", "copyright", "credits" or "license" for more information.
    >>>
    
    • Control-d pour sortir
    • 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): python mon_script.py

  • ipython: interpréteur évolué (avec historique et complétion automatique des commandes):

    $ ipython
    Python 2.7.6 (default, Mar 22 2014, 22:59:56)
    Type "copyright", "credits" or "license" for more information.
    
    IPython 2.2.0 -- An enhanced Interactive Python.
    ?         -> Introduction and overview of IPython's features.
    %quickref -> Quick reference.
    help      -> Python's own help system.
    object?   -> Details about 'object', use 'object??' for extra details.
    
    In [1]:
    
    • Control-d pour sortir
    • Tab pour la complétion automatique
    • Haut et Bas pour le rappel des commandes
    • Aide ipython: object? pour une aide sur un objet, object?? pour une aide plus complète (au niveau source)
    • Commandes magic (voir %magic):
      • %run mon_script.py pour éxecuter un script dans l’interpréteur
      • %debug pour lancer le mode débuggage intéractif post-mortem
      • %cpaste pour coller et éxecuter un code pré-formaté

    Liens:

2.2. Types de base

  • None (rien)
  • Chaînes de caractères: str
    • Entre (simples ou triples) apostrophes ' ou guillemets ": 'Calvin', "Calvin'n'Hobbes", '''Deux\nlignes''', """'Pourquoi?' demanda-t-il."""
    • Conversion: str(3.2)
  • Types numériques:

    • Booléens bool (vrai/faux): True, False, bool(3)
    • Entiers int (pas de valeur limite explicite, correspond au moins au long du C): -2, int(2.1), int("4")
    • Réels float (entre ±1.7e±308, correspond au double du C): 2., 3.5e-6, float(3)
    • Complexes complex: 1+2j, 5.1j, complex(-3.14), complex('j')
    >>> 5 / 2       # ☠☠☠ Division euclidienne par défaut dans Python 2.x
    2
    >>> float(5)/2  # ou ajouter 'from __future__ import division' en début de script
    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)
    
  • Objets itérables:

    • Listes list: ['a', 3, [1, 2], 'a']
    • Listes immuables tuple: (2, 3.1, 'a', []) (selon les conditions d’utilisation, les parenthèses ne sont pas toujours nécessaires)
    • Listes à clés dict: {'a':1, 'b':[1, 2], 3:'c'}
    • Ensembles non ordonnés d’éléments uniques 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)
    >>> 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
    {'a', 1, 2, 3}
    
  • type(obj) retourne le type de l’objet, isinstance(obj, type) teste le type de l’objet.

    >>> type(l)
    <type 'list'>
    >>> isinstance(l, tuple)
    False
    

Liens:

2.3. Structures de programmation

  • Les blocs sont définis par l’indentation (en général par pas de 4 espaces) [1].

    Avertissement

    Évitez autant que possible les caractères de tabulation, source de confusion. Configurez votre éditeur de texte en conséquence.

  • Une instruction par ligne en général (ou instructions séparées par ;).

  • Les commentaires commencent par #, et vont jusqu’à la fin de la ligne.

  • 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: and, or, not

      >>> (5 >= 6) or (not 3 > 4)
      True
      
    • Opérateur ternaire: value if condition else altvalue, p.ex.

      >>> y = x**0.5 if (x > 0) else 0  # Retourne sqrt(max(x, 0))
      
  • Expression conditionnelle: if condition: ... [elif condition2: ...] [else: ...], p.ex.:

    if (i > 0):    # Condition principale
        print "positif"
    elif (i < 0):  # Condition secondaire (si nécéssaire)
        print "négatif"
    else:          # Cas final (si nécéssaire)
        print "nul"
    
  • Boucle for: for element in iterable:, s’éxecute sur chacun des éléments d’un objet itérable:

    >>> for i in range(1, 10, 2): # entiers de 1 (inclus) à 10 (exclu) par pas de 2
    ...     print i**2            # Affichage de i**2
    1
    9
    25
    49
    81
    
    • continue: interrompt l’itération courante, et reprend la boucle à l’itération suivante,
    • break: interrompt complètement la boucle.
  • Boucle while: while condition: se répéte tant que la condition est vraie, ou après une sortie explicite avec 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 plus sur un nombre maximal (raisonnable) d’itérations:

    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!
    

Exercices:

Intégration: méthode des rectangles ★, PGCD: algorithme d’Euclide ★

2.4. Les chaînes de caractères

2.4.1. 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.

Attention

L’indexation en python commence à 0: le 1er élément d’une liste est l’élément n°0, le 2nd 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[25], dernier élément (-2: avant dernier, etc.)
'z'

2.4.2. Sous-liste (slice)

Des portions d’une chaîne peuvent être extraites en utilisant des slices (« tranches »), de notation générique [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[8:23:3] # Du n°8 (inclus) au n°23 (exclu), tous les 3 éléments
'iloru'
>>> alpha[::5]    # Du 1er au dernier élément (défauts), tous les 5 éléments
'afkpuz'

2.4.3. Méthodes

Les chaînes de caractères disposent de nombreuses fonctionnalités – appelées « méthodes » en POO – facilitant leur manipulation:

>>> enfant, peluche = "Calvin", 'Hobbes'      # Affectation mutiple
>>> titre = enfant + ' et ' + peluche; titre  # +: Concaténation de chaînes
'Calvin et Hobbes'
>>> titre.replace('et', '&')        # Remplacement de sous-chaînes
'Calvin & 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

2.4.4. Formatage

Le système de formatage permet un contrôle précis de la conversion de variables en chaînes de caractères. Il s’appuie essentiellement sur la méthode str.format():

>>> "{} a {} ans".format('Calvin', 6)
'Calvin a 6 ans'
>>> pi = 3.1415926535897931
>>> "{x:f}  {x:.2f}  {y:f}  {y:g}".format(x=pi, y=pi*1e9)
'3.141593  3.14  3141592653.589793  3.14159e+09'

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 "{0} fois {1} font {2}".format(3, 4, 3*4) # Formatage et affichage
3 fois 4 font 12

Exercice:

Tables de multiplication ★

2.5. Objets 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 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 = range(1, 6) # De 1 (inclus) à 6 (exclu) = [1, 2, 3, 4, 5]
    >>> 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 commençe à 0)
    (1, 4)
    >>> 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 2ème ('1') *inclus* au dernier ('-1') *exclu*
    [2, 3, 4]
    >>> l[1:-1:2]      # Idem, tous les 2 éléments
    [2, 4]
    >>> l[::2]         # Tous les 2 éléments (*start=0* et *stop=len* par défaut)
    [1, 3, 5]
    
  • Modification d’éléments d’une liste (chaînes et tuples sont immuables):

    >>> l[0] = 'a'; l            # Remplacement du 1er élément
    ['a', 2, 3, 4, 5]
    >>> l[1::2] = ['x', 'y']; l  # Remplacement d'éléments par *slices*
    ['a', 'x', 3, 'y', 5]
    >>> l + [1, 2]; l            # Concaténation (l reste inchangé)
    ['a', 'x', 3, 'y', 5, 1, 2]
    ['a', 'x', 3, 'y', 5]
    >>> l += [1, 2]; l           # Concaténation sur place (l est modifié)
    ['a', 'x', 3, 'y', 5, 1, 2]
    >>> l.append('z'); l         # Ajout d'un élément en fin de liste
    ['a', 'x', 3, 'y', 5, 1, 2, 'z']
    >>> l.extend([-1, -2]); l    # Extension par une liste
    ['a', 'x', 3, 'y', 5, 1, 2, 'z', -1, -2]
    >>> del l[-6:]; l            # Efface les 6 derniers éléments de la liste
    ['a', 'x', 3, 'y']
    

    Attention

    à la modification des objets mutables:

    >>> l = range(3)  # l pointe vers la liste [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]]
    
  • Utilitaires sur les itérables:

    >>> l1 = ['Calvin', 'Wallace', 'Boule']
    >>> for i in range(len(l1)):     # Boucle sur les indices de l1
    ...     print i, l1[i]           # Accès explicite, pas pythonique :-(
    0 Calvin
    1 Wallace
    2 Boule
    >>> for i, name in enumerate(l1): # Boucle sur (indice, valeur) de l1
    ...     print i, name             # Pythonique :-D
    0 Calvin
    1 Wallace
    2 Boule
    >>> l2 = ['Hobbes', 'Gromit', 'Bill']
    >>> for n1, n2 in zip(l1, l2):    # Boucle simultanée sur 2 listes (ou +)
    ...     print n1, 'et', n2
    Calvin et Hobbes
    Wallace et Gromit
    Boule et Bill
    >>> sorted(zip(l1, l2)) # Tri, ici sur le 1er élément de chaque tuple de la liste
    [('Boule', 'Bill'), ('Calvin', 'Hobbes'), ('Wallace', 'Gromit')]
    

Exercices:

Crible d’Ératosthène ★, Carré magique ★★

2.6. Fonctions

Une fonction est un regroupement d’instructions impératives – assignations, branchements, boucles – s’appliquant sur des arguments d’entrée. C’est le concept central de la programmation impérative.

def permet de définir une fonction: 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 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é return.

Exemples:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def temp_f2c(tf):
    """
    Conversion d'une température Fahrenheit `tf` en température Celsius.

    Exemple:
    >>> temp_f2c(104)
    40.0
    """

    tc = (tf - 32.)/1.8       # Conversion Fahrenheit vers 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 Développement piloté par les tests).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def mean_power(alist, power=1):
    """
    Retourne la racine `power` de la moyenne des éléments de `alist` à
    la puissance `power`:

    .. math:: \mu = (\frac{1}{N}\sum_{i=0}^{N-1} x_i^p)^{1/p}

    `power=1` correspond à la moyenne arithmétique, `power=2` au *Root
    Mean Squared*, etc.

    Exemples:
    >>> mean_power([1, 2, 3])
    2.0
    >>> mean_power([1, 2, 3], power=2)
    2.160246899469287
    """

    s = 0.                  # Initialisation de la variable *s* comme *float*
    for val in alist:       # Boucle sur les éléments de *alist*
        s += val ** power   # *s* est augmenté de *val* puissance *power*
    s /= len(alist)         # = somme valeurs / nb valeurs
    # *mean* = (somme valeurs / nb valeurs)**(1/power)
    mean = s ** (1 / power)

    return mean

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.).
  • 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 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 [2]. 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.

Exercice:

Suite de Syracuse (fonction) ★

2.7. Bibliothèques et scripts

2.7.1. Bibliothèques externes

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 math définit les fonctions et constantes mathématiques usuelles (sqrt(), pi, etc.)

Une bibliothèque est « importée » avec la commande import module. Les fonctionnalités supplémentaires sont alors accessibles dans l’espace de noms module via 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

Attention

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! (possibilité de conflit dans les espaces de noms).

Nous verrons par la suite quelques exemples de modules de la Bibliothèque standard, ainsi que des Bibliothèques numériques orientées analyse numérique.

Exercice:

Flocon de Koch (programmation récursive) ★★★

2.7.2. 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 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).

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__,
  • ou un exécutable – s’il inclut un __main__ ou des instructions exécutables,
  • ou les deux à la fois.

Exemple:

Le code 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 [3]:

    #!/usr/bin/env python
    
  • Un fichier incluant des caractères non-ASCII (p.ex. caractères accentués, ou symboles UTF tel que ±) doit définir le système d’encodage, généralement utf-8:

    # -*- coding: utf-8 -*-
    

    Notez que les noms de variables, fonctions, etc. doivent être purement ASCII (a-zA-Z0-9_). De manière générale, favorisez la langue anglaise (variables, commentaires, affichages).

  • """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 1ère instruction du script.

  • from __future__ import division: permet de ne pas considérer les divisions entre entiers comme euclidiennes par défaut.

  • 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.

2.8. Exceptions

Lorsqu’il rencontre une erreur dans l’exécution d’une instruction, l’interpréteur Python génère (raise) une erreur (Exception), de nature différente selon la nature de l’erreur: KeyError, ValueError, AttributeError, NameError, TypeError, IOError, NotImplementedError, 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 EAFP [4]: 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 Exception se fait par la construction try ... except.

1
2
3
4
5
6
7
8
def lireEntier():
    while True:
        try:
            chaine = raw_input('Entrez un entier: ') # Lecture du clavier
            # La conversion en type entier lève `ValueError` si impossible
            return int(chaine)
        except ValueError:             # Gestion de l'exception ValueError
            print "'{}' n'est pas un entier".format(chaine)
>>> 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

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 replis, et laissez passer les autres (ce qui provoquera éventuellement l’interruption du programme)

Attention

Évitez à tout prix les 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 raise Exception – si elles ne peuvent conclure leur action, à charge pour la procédure appelante de les gérer si besoin:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
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("x={} < y={}".format(x, y))

    return x**2 - y**2

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’assert (qui, si l’hypothèse n’est pas vérifiée, génère une AssertionError):

1
2
3
4
5
6
7
def diff_sqr(x, y):
    """
    Returns x**2 - y**2 for x >= y, AssertionError otherwise.
    """

    assert x >= y, "x={} < y={}".format(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!

Exercice:

Jeu du plus ou moins (exceptions) ★

2.9. Classes

Un objet est une entité de programmation, disposant de ses propres états et fonctionnalités. 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 Forme peut représenter une forme plane caractérisée par sa couleur, et disposant de fonctionnalités propres, p.ex. change_couleur().

  • Instantiation: c’est le fait générer un objet concret (une instance) à partir d’un modèle (une classe). P.ex. rosie = Forme('rose') crée une instance rosie à partir de la classe Forme et d’une couleur (chaîne de caractères 'rose').

  • Attributs: variables internes décrivant l’état de l’objet. P.ex., rosie.couleur donne la couleur de la Forme rosie.

  • 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. rosie.change_couleur('bleu') change la couleur de la Forme rosie.

    Attention

    Toutes les méthodes d’une classe doivent au moins prendre self – représentant l’objet lui-même – 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 Forme pour que les opérations du genre forme1 < forme2 aient un sens (p.ex. en comparant les aires).

    Liste des méthodes standard et des surcharges d’opérateur

  • 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 Rectangle hérite de la classe Forme (elle partage la notion de couleur et d’aire), et lui ajoute des méthodes propres à la notion de rectangle (p.ex. formule explicite de l’aire, étirement).

    Attention

    Toutes les classes doivent au moins hériter de la classe principale object.

Exemple de définition de classe

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class Forme(object):  # *object* est la classe dont dérivent toutes les autres

    """Une forme plane, avec éventuellement une couleur."""

    def __init__(self, couleur=None):
        """Initialisation d'une Forme, sans couleur par défaut."""

        if couleur is None:
            self.couleur = 'indéfinie'
        else:
            self.couleur = couleur

    def __str__(self):
        """
        Surcharge de la fonction `str()`: l'affichage *informel* de
        l'objet dans l'interpréteur, p.ex. `print a` sera résolu comme
        `a.__str__()`

        Retourne une chaîne de caractères.
        """

        return "Forme encore indéfinie de couleur {}".format(self.couleur)

    def change_couleur(self, newcolor):
        """Change la couleur de la Forme."""

        self.couleur = newcolor

    def aire(self):
        """
        Renvoi l'aire de la Forme.

        L'aire ne peut pas être calculée dans le cas où la forme n'est
        pas encore spécifiée: c'est ce que l'on appelle une méthode
        'abstraite', qui pourra être précisée dans les classes filles.
        """

        raise NotImplementedError(
            "Impossible de calculer l'aire d'une forme indéfinie.")

    def __cmp__(self, other):
        """
        Comparaison de deux Formes sur la base de leur aire.

        Surcharge des opérateurs de comparaison de type `{self} <
        {other}`: la comparaison sera résolue comme
        `self.__cmp__(other)` et le résultat sera correctement
        interprété.

        .. WARNING:: cette construction n'est plus supportée en Python3.
        """

        return cmp(self.aire(), other.aire())  # Opérateur de comparaison

Exemple d’héritage de classe

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class Rectangle(Forme):

    """
    Un Rectangle est une Forme particulière.

    La classe-fille hérite des attributs et méthodes de la
    classe-mère, mais peut les surcharger (i.e. en changer la
    définition), ou en ajouter de nouveaux:

    - les méthodes `Rectangle.change_couleur()` et
      `Rectangle.__cmp__()` dérivent directement de
      `Forme.change_couleur()` et `Forme.__cmp__()`;
    - `Rectangle.__str__()` surcharge `Forme.__str__()`;
    - `Rectangle.aire()` définit la méthode jusqu'alors abstraite
      `Forme.aire()`;
    - `Rectangle.allonger()` est une nouvelle méthode propre à
      `Rectangle`.
    """

    def __init__(self, longueur, largeur, couleur=None):
        """
        Initialisation d'un Rectangle longueur × largeur, sans couleur par
        défaut.
        """

        # Initialisation de la classe parente (nécessaire pour assurer
        # l'héritage)
        Forme.__init__(self, couleur)

        # Attributs propres à la classe Rectangle
        self.longueur = longueur
        self.largeur = largeur

    def __str__(self):
        """Surcharge de `Forme.__str__()`."""

        return "Rectangle {}x{}, de couleur {}".format(
            self.longueur, self.largeur, self.couleur)

    def aire(self):
        """
        Renvoi l'aire du Rectangle.

        Cette méthode définit la méthode abstraite `Forme.area()`,
        pour les Rectangles uniquement.
        """

        return self.longueur * self.largeur

    def allonger(self, facteur):
        """Multiplie la *longueur* du Rectangle par un facteur"""

        self.longueur *= facteur

Note

Il est traditionnel de commencer les noms de classes avec des majuscules (Forme), et les noms d’instances de classe (les variables) avec des minuscules (rosie).

Exemples

Formes (POO), Cercle circonscrit (POO, argparse)

Études de cas

Exercices:

Animaux (POO/TDD) ★, Points matériels et ions (POO/TDD) ★, Jeu de la vie (POO) ★★

2.10. Entrées-sorties

2.10.1. Intéractif

Comme nous avons pu le voir précédemment, l’affichage à l’écran se fait par print, la lecture du clavier par raw_input.

2.10.2. Fichiers

La gestion des fichiers (lecture et écriture) se fait à partir de la fonction open() retournant un objet file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# ========== ÉCRITURE ==========
outfile = open("carres.dat", 'w') # Ouverture du fichier "carres.dat" en écriture
for i in range(1, 10):
    outfile.write("{}  {}\n".format(i, i**2)) # 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 "Cannot decipher line '{}'".format(line)
        continue
    print "{}**3 = {}".format(x, x**3)

2.11. Éléments passés sous silence

Cette (brève) introduction à Python se limite à des fonctionnalités relativement simples du langage. De nombreuses fonctionnalités du langage n’ont pas été abordées:

  • Variables globales
  • Arguments anonymes: *args and **kwargs
  • Fonction anonyme: lambda x, y: x + y
  • Itérateurs et générateurs: yield
  • Gestion de contexte: with ... as (PEP 243)
  • Décorateurs: fonction sur une fonction ou une classe (@staticmethod, etc.)
  • Héritages multiples et méthodes de résolution
  • Etc.

Ces fonctionnalités peuvent évidemment être très utiles, mais ne sont généralement pas strictement indispensables pour une première utilisation de Python dans un contexte scientifique.

2.11.1. Python 3.x

Pour des raisons historiques, ce cours présente le langage Python dans sa version 2.x. Pourtant, le développement actuel de Python se fait uniquement sur la branche 3.x, qui constitue une remise à plat non-rétrocompatible du langage, et la branche 2.x ne sera a priori plus supporté au delà de 2020 (PEP 466). Il est donc préférable, si vous vous lancez dans un développement substantiel, de passer aussi rapidement que possible à Python 3 (voir Python 2 vs. python 3).

Python 3 apporte quelques changements fondamentaux, notamment:

  • print n’est plus un mot-clé mais une fonction: print(...)
  • l’opérateur / ne réalise plus la division euclidienne entre les entiers, mais toujours la division réelle

L’interpréteur Python 2.7 dispose d’une option -3 mettant en évidence dans un code les parties qui devront être modifiées pour un passage à Python 3.

Notes de bas de page

[1]ou from __future__ import braces :-)
[2]La notion de « portée » est plus complexe, je simplifie...
[3]Il s’agit d’une fonctionnalité des shells d’Unix, pas specifique à Python.
[4]Par opposition au LBYL du C/C++, basé sur une série exhaustive de tests a priori.