3. Initiation à Python🔗

3.1. 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 (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)
    
  • 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)
    >>> 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, 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]
    
  • 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:

3.2. Structures de programmation🔗

  • Les blocs sont définis par l”indentation (en général par pas de quatre espaces) 1.

    Avertissement

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

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

      >>> x = 3
      >>> not ((x <= 0) or (x > 5))
      True
      >>> 0 < x <= 5   # Conditions chaînées
      True
      
    • Opérateur ternaire (PEP 308): 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écessaire)
        print("négatif")
    else:          # Cas final (si nécessaire)
        print("nul")
    
  • Boucle for: 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
    
    • continue: interrompt l’itération courante, et reprend la boucle à l’itération suivante,

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

  • Boucle while: while condition: se répéte tant que la condition est vraie, ou jusqu’à 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 outre 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!
    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 do ... while condition; cette dernière peut être remplacée par:

while True:
    # calcul de la condition d'arrêt
    if condition:
        break

Exercices:

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

3.3. Les chaînes de caractères🔗

3.3.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 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'

3.3.2. Sous-liste (slice)🔗

Des portions d’une chaîne peuvent être extraites en utilisant des slice (« 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[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'

3.3.3. Méthodes🔗

Comme la plupart des objets en Python, les chaînes de caractères disposent de nombreuses fonctionnalités – appelées « méthodes » en POO – 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']

3.3.4. Formatage🔗

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 2, le système de choix est dorénavant (Python 3.6+) celui de la chaîne formatée (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 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'

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

Exercice:

Tables de multiplication ★

3.4. 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 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}
    
  • 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')]
    

Exercices:

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

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

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):
    """
    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 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
def mean_power(alist, power=1):
    r"""
    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
    """

    # *mean* = (somme valeurs**power / nb valeurs)**(1/power)
    mean = (sum( val ** power for val in alist ) / len(alist)) ** (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.): c’est ce que l’on appelle le duck-typing 3, 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 4. 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 7 (a-zA-Z0-9_); de manière générale, favorisez plutôt la langue anglaise (variables, commentaires, affichages).

Exercice:

Suite de Syracuse (fonction) ★

3.6. Bibliothèques et scripts🔗

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

Avertissement

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 Bibliothèque standard, ainsi que des Bibliothèques numériques de base orientées analyse numérique.

Exercice:

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

3.6.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 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__ 5;

  • 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 6:

    #!/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.

3.7. 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, 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 EAFP 8: 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.

1
2
3
4
5
6
7
8
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

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

Exercice:

Jeu du plus ou moins (exceptions) ★

3.8. Classes🔗

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

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
class Animal:
    """
    Un animal, défini par sa masse.
    """

    def __init__(self, masse):
        """
        Initialisation d'un Animal, a priori vivant.

        :param float masse: masse en kg (> 0)
        :raise ValueError: masse non réelle ou négative
        """

        self.estVivant = True

        self.masse = float(masse)
        if self.masse < 0:
            raise ValueError("La masse ne peut pas être négative.")


    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__()`

        :return: une chaîne de caractères
        """

        return f"Animal {'vivant' if self.estVivant else 'mort'}, " \
            f"{self.masse:.0f} kg"


    def meurt(self):
        """
        L'animal meurt.
        """

        self.estVivant = False


    def grossit(self, masse):
        """
        L'animal grossit (ou maigrit) d'une certaine masse (valeur algébrique).

        :param float masse: prise (>0) ou perte (<0) de masse.
        :raise ValueError: masse non réelle.
        """

        self.masse += float(masse)

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
class AnimalFeroce(Animal):
    """
    Un animal féroce est un animal qui peut dévorer d'autres animaux.

    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:

    - la méthode `AnimalFeroce.__init__()` dérive directement de
      `Animal.__init__()` (même méthode d'initialisation);
    - `AnimalFeroce.__str__()` surcharge `Animal.__str__()`;
    - `AnimalFeroce.devorer()` est une nouvelle méthode propre à
      `AnimalFeroce`.
    """

    def __str__(self):
        """
        Surcharge de la fonction `str()`.
        """

        return "Animal féroce " \
            f"{'bien vivant' if self.estVivant else 'mais mort'}, " \
            f"{self.masse:.0f} kg"

    def devore(self, other):
        """
        L'animal (self) devore un autre animal (other).

        * Si other est également un animal féroce, il faut que self soit plus
          gros que other pour le dévorer. Sinon, other se défend et self meurt.
        * Si self dévore other, other meurt, self grossit de la masse de other
          (jusqu'à 10% de sa propre masse) et other maigrit d'autant.

        :param Animal other: animal à dévorer
        :return: prise de masse (0 si self meurt)
        """

        if isinstance(other, AnimalFeroce) and (other.masse > self.masse):
            # Pas de chance...
            self.meurt()
            prise = 0.
        else:
            other.meurt()             # Other meurt
            prise = min(other.masse, self.masse * 0.1)
            self.grossit(prise)       # Self grossit
            other.grossit(-prise)     # Other maigrit

        return prise
 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
class AnimalGentil(Animal):
    """
    Un animal gentil est un animal avec un petit nom.

    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:

    - la méthode `AnimalGentil.__init__()` surcharge l'initialisation originale
      `Animal.__init__()`;
    - `AnimalGentil.__str__()` surcharge `Animal.__str__()`;
    """

    def __init__(self, masse, nom='Youki'):
        """
        Initialisation d'un animal gentil, avec son masse et son nom.
        """

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

        # Attributs propres à la classe AnimalGentil
        self.nom = nom

    def __str__(self):
        """
        Surcharge de la fonction `str()`.
        """

        return f"{self.nom}, un animal gentil " \
            f"{'bien vivant' if self.estVivant else 'mais mort'}, " \
            f"{self.masse:.0f} kg"

    def meurt(self):
        """
        L'animal gentil meurt, avec un éloge funéraire.
        """

        Animal.meurt(self)
        print(f"Pauvre {self.nom} meurt, paix à son âme...")

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

Exemples

Animal (POO), Cercle circonscrit (POO, argparse)

Études de cas

Exercices:

Animaux (POO/TDD) ★, Jeu de la vie (POO) ★★

3.9. Entrées-sorties🔗

3.9.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 input.

3.9.2. Fichiers texte🔗

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

 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 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}")

Notes de bas de page

1

ou from __future__ import braces :-)

2

Utilisation native du % avec la grammaire C printf, et plus récemment de la méthode de formatage des chaînes str.format(); ces deux options sont encore valables et largement utilisées.

3

If it looks like a duck and quacks like a duck, it must be a duck.

4

La notion de « portée » est plus complexe, je simplifie…

5

Parfois prononcé dunder main (dunder désigne le double _).

6

Il s’agit d’une fonctionnalité des shells d’Unix, pas spécifique à Python.

7

En fait, Python 3 gère nativement les caractères Unicode:

>>> α, β = 3, 4
>>> print("α² + β² =", α**2 + β**2)
α² + β² = 25
8

Par opposition au LBYL du C/C++, basé sur une série exhaustive de tests a priori.