Introduction à la programmation en Python

par Jean-Daniel Bonjour

version 2021-08-25 - © Creative Commons BY-SA

1 Introduction

1.1 Avant-propos

Ce support de cours a pour objectif de vous introduire à la programmation Python en se basant essentiellement sur des exemples et partant du principe que vous connaissez déjà d’autre(s) langage(s) de programmation. Nous ferons parfois quelques parallèles avec MATLAB / GNU Octave, langages généralement bien connus des ingénieurs.

 

important Nous avons résolument opté, dans ce support de cours, pour la version 3 de Python. Lorsque des différences importantes existent entre Python v2 et v3, nous les signalons avec ce symbole python2. Avec la version 3 de Python, apparue en 2008, la fondation Python a en effet décidé de gommer certaines imperfections de jeunesse du langage. La compatibilité arrière avec les versions ≤ 2 n’est donc pas garantie, un programme écrit en Python v2 ne tournant généralement pas sans adaptations sous un interpréteur Python v3. Cette version 3 étant cependant disponible depuis suffisamment longtemps, la plupart des modules, packages, frameworks… ont été adaptés, et nous estimons il est temps de passer définitivement à Python v3.

Nous faisons usage, dans ce support de cours, des conventions de notation suivantes :


Sous licence Creative Commons BY-SA, ce support de cours est accessible en ligne à l’adresse https://www.jdbonjour.ch/cours/python/introduction/. Une version PDF formatée pour une belle impression A4 est également disponible sous la forme des deux fichiers téléchargeables suivants : introduction-python.pdf et outils-python.pdf. L’auteur reçoit volontiers toutes vos remarques (corrections, suggestions de compléments…).

Pour information, ce support de cours a été édité dans le langage de balisage léger Markdown, puis traduit en HTML avec le convertisseur Pandoc. Si ces techniques vous intéressent, voyez le manuel Markdown/Pandoc que nous avons réalisé.

1.2 Historique et caractéristiques du langage Python

Quelques dates dans l’histoire de Python (pour davantage de détails, frappez license()) :

Principales caractéristiques du langage Python :

Ces différentes qualités expliquent pourquoi de plus en plus d’écoles et universités optent, depuis quelques années, en faveur de Python comme premier langage dans l’apprentissage de la programmation. En outre, avec Jupyter Notebook et les librairies NumPy et SciPy notamment, nombre de chercheurs ont aujourd’hui trouvé en Python une véritable alternative à MATLAB, en particulier dans les domaines du calcul numérique, de l’analyse et de la visualisation de données.

1.3 Quelques références

La littérature sur Python est extrêmement abondante. Nous ne retenons ici que quelques références significatives :

2 Bases de la programmation en Python

Pour des conseils concernant l’installation de Python sur votre machine, voyez notre chapitre “Installation d’un environnement Python v3 complet”.

2.1 Généralités

2.1.1 En-têtes d’un script

Nous verrons plus loin ce que sont précisément les scripts Python. Pour simplifier et comprendre les exemples qui suivent, disons à ce stade qu’il s’agit de fichiers au format texte dont le nom se termine par l’extension .py, contenant des instructions Python et débutant en général par 2 lignes d’en-têtes.

Interpréteur du script

La première ligne d’en-tête du script (appelée she-bang en raison des 2 premiers caractères # et !) spécifie l’interpréteur qui doit être utilisé pour l’exécuter. S’agissant de Python, vous verrez parfois : #!/usr/bin/python, mais cette forme est moins portable que : #!/usr/bin/env python (exemple ci-contre) qui fonctionne indépendamment de l’emplacement (path) d’installation de l’interpréteur Python.

Sous GNU/Linux Ubuntu, pour explicitement utiliser l’interpréteur Python v3 provenant des dépôts officiels Ubuntu, il faut utiliser le she-bang #!/usr/bin/env python3.

#!/usr/bin/env python
# -*- coding: utf­-8 ­-*-

print("Hééé, bien le bonjour !")

Cette première ligne est nécessaire si l’on veut pouvoir lancer le script depuis un shell en frappant : ./nom_du_script.py. Elle n’est cependant pas nécessaire (mais tout de même recommandée) si on le lance avec python nom_du_script.py ou python3 nom_du_script.py

Encodage des caractères

Pour faire usage de caractères accentués dans un script Python (même si l’on n’utilise ceux-ci que dans des commentaires !), il peut être nécessaire de définir, mais pas plus loin que la seconde ligne du fichier, un pseudo-commentaire coding (voir l’exemple ci-dessus).

Depuis Python v3 (qui a systématisé Unicode pour la gestion des chaînes de caractères) :


python2 Sous Python v2 (où l’encodage par défaut est ASCII) :

Explications techniques : Unicode est le jeu de caractères universel prenant en compte les écritures de tous les pays du monde. UTF-8 est un encodage Unicode dans lequel un caractère est codé respectivement sur 1, 2 ou 4 octets selon qu’il fait partie de la plage ASCII sans accents (code ≤ 127), de la plage d’extension (128 ≤ code ≤ 255) ou des plages universelles (256 ≤ code).

2.1.2 Instructions sur plusieurs lignes, commentaires

Pour des raisons de lisibilité, une instruction Python ne devrait pas dépasser environ 80 caractères. On peut si nécessaire la répartir sur plusieurs lignes en utilisant, en fin de lignes, le caractère de continuation de ligne \ (back-slash).

Cependant les parties de code délimitées par ( ), [ ], { } (tuples, listes, dictionnaires…), triple apostrophe ''' ou triple guillemets """ (chaînes multi-lignes) peuvent s’étendre sur plusieurs lignes sans faire usage du caractère \.

print("Affichage d'une longue ligne \
de texte. Blabla blabla blabla \
blabla blabla blabla blabla blabla.")

liste = ['un', 'deux', 'trois',
         'quatre', 'cinq', 'six']

chaine = """Une chaîne de caractères
multi-lignes"""   # intégrant le caractère de
                  # retour à la ligne \n

Pour insérer des commentaires dans du code Python :

# Commentaire d'une ligne

print('x =', x) # Commentaire après instruction

'''Un commentaire chevauchant
   plusieurs lignes...'''

"""et un autre commentaire
   multi-ligne"""

2.1.3 Variables, mots-clés réservés

important Les noms de variables (ou de tout autre objet Python tel que fonction, classe, module…) débutent par une lettre majuscule ou minuscule (A-Z ou a-z) ou _ (souligné) suivie de 0, 1 ou plusieurs lettres, chiffres ou soulignés. Ils sont sensibles aux majuscules/minuscules (VAR1 et var1 désignant ainsi deux variables distinctes). Depuis Python v3, on pourrait utiliser tout caractère Unicode, mais il est préférable de s’en ternir aux caractères ASCII (donc sans caractères accentués).

Les mots-clés ci-contre (que vous pouvez afficher dans un interpréteur Python avec la commande help("keywords")) sont réservés et ne peuvent donc pas être utilisés pour définir vos propres identifiants (variables, noms de fonction, classes…).

    False       def         if          raise
    None        del         import      return
    True        elif        in          try
    and         else        is          while
    as          except      lambda      with
    assert      finally     nonlocal    yield
    break       for         not                 
    class       from        or                  
    continue    global      pass                

Pour le reste, Python ne protège pas l’utilisateur contre l’écrasement de noms. Par exemple le fait de définir un objet abs=12 masque la fonction built-in abs() (valeur absolue d’un nombre), fonction que l’on retrouvera en déréférençant l’objet avec del abs.

2.1.4 Typage

Python est un langage à typage dynamique fort :

Pour vérifier le type d’une variable (ou plutôt le type de la donnée/objet référencé par la variable), on utilisera la fonction type(objet). Sous IPython, on peut aussi utiliser la commande %whos.

2.1.5 Types simples

Python propose 4 types simples de base (built-in). Ils sont dits simples (ou sclaires, atomiques) car ils permettent de stocker une seule donnée par variable, contrairement aux types containers. Il s’agit de :

On verra plus loin que ces 4 types sont immutables (contrairement par exemple aux listes et dictionnaires).

On dispose de fonctions d’arrondi et de conversion, notamment :

Pour récupérer la valeur absolue d’un entier ou flottant, on dispose de la fonction habituelle abs(entier_ou_flottant).

# bool : booléen (logique)
vlog = True
type(vlog)        # => builtins.bool
not vlog          # => False
  # notez le 1er car. majuscule de True et False !

# int : entier de précision illimitée !
a = 2 ; i = -12
v = 2**80         # => 1208925819614629174706176
  # définition d'entiers en binaire, octal ou hexadéc.:
k = 0b111         # => 7
m = 0o77          # => 63
n = 0xff          # => 255
  # conv. chaînes bin/octal/hexa en entier & vice-versa:
int('111',2)  # => 7,   et inverse: bin(7)   => '0b111'
int('77',8)   # => 63,  et inverse: oct(63)  => '0o77'
int('ff',16)  # => 255, et inverse: hex(255) => '0xff'

# float : flottant 64 bits
b = -3.3 ; r = 3e10
abs(b)            # => 3.3
  # arrondi et conversion
int(3.67)         # => 3 (int)
int(-3.67)        # => -4 (int)
round(3.67)       # => 4 (int), comme round(3.67, 0)
round(3.67,1)     # => 3.7 (float)
round(133,-1)     # => 130 (int)
round(133,-2)     # => 100 (int)

# complex : complexe flottant
cplx = 3.4 + 2.1j # ou:  cplx = complex(3.4, 2.1)
cplx.real         # => 3.4
cplx.imag         # => 2.1

Vous aurez remarqué dans ces exemples que, contrairement à d’autres langages, il n’y a pas de ; (point-virgule) à la fin des instructions. Cela ne serait nécessaire que si l’on souhaite définir plusieurs instructions sur la même ligne, ce qui est rarement pratiqué en Python et déconseillé pour des raisons de lisibilité.

2.1.6 Assignation simple et multiple

On vient de voir plus haut que l’assignation de variables s’effectue classiquement avec =

Python permet de faire aussi de l’assignation multiple, c’est-à-dire :

x1 = x2 = x3 = 3  # => x1, x2, x3 prennent la val. 3

y1,y2,y3 = 4,5,6  # => y1=4, y2=5, y3=6
y1, y2 = y2, y1   # permutation => y1=5, y2=4

tpl = (10,'abc')  # ici un tuple...
v1, v2 = tpl      # => v1=10, v2='abc'

On peut déréférencer une donnée (ou tout type d’objet) avec del objet ou del(objet). Pour déréférencer un attribut, on utilise del objet.attr ou delattr(objet,'attr').

Explications techniques : Python accède aux données en mode “référence”. Soit une variable à laquelle on a assigné une valeur. Si on lui assigne ensuite une autre valeur, la variable se “rèfère” dès lors à cette nouvelle donnée. Qu’advient-il alors de l’ancienne donnée ? Pour autant que plus aucune variable ne s’y réfère, le “garbage collector” la supprimera alors automatiquement, libérant ainsi de la mémoire. Sachant cela, on comprend mieux le mécanisme de “typage dynamique” de Python : une variable n’a en elle-même pas de type, elle a celui de la donnée à laquelle elle se réfère couramment !

v1 = 123 # => création donnée 123 référencée par v1
type(v1) #    l'OBJET référencé de type builtins.int
id(v1)   # => 9361312 (adresse mémoire)

v2 = v1  # => nouv. variable pointant sur même DONNÉE
id(v2)   # => 9361312 => pointe bien sur MÊME donnée !

v1 = 'a' # => création donnée 'a', et chang. réf. v1
type(v1) #    l'OBJET référencé de type builtins.str
id(v1)   # => 9361332 => v1 pointe bien s/AUTRE donnée

del v2   # => la donnée 123 n'est plus référencée (ni
         #    par v1 ni v2) => le garbage collector
         #    pourra supprimer donnée de la mémoire

2.1.7 Interaction avec l’utilisateur (écran et clavier)

Affichage dans la console (sortie standard)

Pour afficher quelque-chose sur la console, on utilise la fonction print dont la syntaxe générale est :

print(arguments, sep=' ', end='\n', file=sys.stdout)

Seuls le(s) argument(s) sont nécessaires, les autres paramètres sep, end et file étant facultatifs. Par défaut print écrit les argument(s) sur la sortie standard, ajoutant un caractère espace entre chaque argument, et envoie finalement un newline (saut de ligne). Mais on peut librement modifier les valeurs de ces paramètres.

Notez qu’on peut passer autant d’argument(s) que l’on veut (contrairement à la fonction disp de MATLAB/Octave qui est limitée à 1 argument).

print('Le produit de', a, 'et', b, 'est', a*b)
print('Total des %s : %.2f CHF' % ('Dépenses',123))

# Ci-dessous, résultats équivalents :
print('Hello world !')
print('Hello ',end=''); print('world !')


# Sous Python v2 on aurait écrit :
print 'Hello',  # la virgule élimine le saut de ligne
print 'world !'

important Remarque : on a vu que le nommage des fonctions répond aux mêmes règles que les variables. Les noms de fonctions sont donc également sensibles aux majuscules/minuscules (PRINT ou Print retournant ainsi une erreur de type “… is not defined”).

python2 Sous Python ≤ 2, print était une commande et non pas une fonction. Les arguments étaient donc passé à la suite de la commande sans parenthèses. Pour supprimer le saut de ligne, il fallait faire suivre le dernier argument d’une virgule.

Lecture au clavier (entrée standard)

À partir de Python v3, on utilise pour cela la fonction input(prompt) qui affiche le prompt puis retourne ce que l’utilisateur a frappé sous forme de chaîne (dans tous les cas !).

Notez, dans les exemples ci-contre, l’usage des fonctions de conversion int en entier et float en réel, ainsi que de eval qui évalue une expression Python et retourne son résultat.

nom = input('Votre nom ? ')
    # retourne une chaîne
poids = float(input('Votre poids ? '))
    # génère une erreur si on saisit une chaîne
annee = int(input('Votre année de naissance ? '))
    # génère erreur si saisie chaîne ou flottant


res = eval(input('Entrer expression Python : '))
    # => évaluation de l'expression (comme input MATLAB)

python2 Sous Python ≤ 2, on disposait des fonctions raw_input pour saisir une chaîne, et input pour saisir un nombre, une variable ou une expression. L’ancien comportement de input (correspondant au input sans second paramètre 's' de MATLAB/Octave) est obtenu sous Python v3 avec eval(input(...)).

2.1.8 Opérateurs de base

Les opérateurs mathématiques de base sont :

On verra que + et * s’appliquent aussi aux séquences (chaînes, tuples et listes), et - aux ensembles (sets et frozensets).

La fonction divmod(a, b) retourne un tuple contenant (a//b, a % b)

2*3   # => 6 entier, ou: int(2.)*3
2.*3  # => 6.0 réel, ou: float(2)*3
13/5  # => 2.6 (aurait retourné 2 sous Python v2)
13//5 # => 2   (div. int. tronquée comme / en Python v2)
10**2 # => 100
13%5  # => 3   (reste de la division entière)
divmod(13,5)  # => (2, 3)

'oh' + 2*'là'   # => 'ohlàlà'
(1,2) + 2*(4,)  # => (1, 2, 4, 4)

Depuis Python v3, la division / de deux nombres entiers retourne toujours un flottant, et c’est donc à l’utilisateur de le convertir si nécessaire en entier avec int (troncature) ou round (arrondi).

python2 Sous Python ≤ 2, la division / de deux nombres entiers retournait toujours un entier qui pouvait donc être tronqué, comme le fait la division // Python v3.

Les opérateurs de pré/post incrémentation/décrémentation ++ et -- n’existant pas sous Python, on utilisera += 1 et -= 1

val = 10
val += 1      # val=val+1   => 11
val -= 1      # val=val-1   => 10
val *= 2.5    # val=val*2.5 => 25.0
val /= 10     # val=val/10  => 2.5
val %= 2      # val=val%2   => 0.5

fruit = 'pomme'
fruit += 's'  # => 'pommes'
tpl = (1,2,3) # ici un tuple
tpl += (4,)   # => (1, 2, 3, 4)
tpl *= 2      # => (1, 2, 3, 4, 1, 2, 3, 4)

2.1.9 Opérateurs de comparaison et logiques

Python offre notamment les opérateurs suivants :

Ces opérateurs retournent les valeurs True ou False de type booléen. Notez qu’il n’est pas nécessaire d’entourer les expressions logiques de parenthèses comme dans d’autres langages.

1 < 2  and  2 < 3                # => True
'oui' == 'OUI'.lower()           # => True
False  or   not True  and  True  # => False

[1,2,3] == [1,2,3]               # => True
[1,3,2] == [1,2,3]               # => False

a=[1,2] ; b=[1,2]; a is b        # => False
a=[1,2] ; b=a;     a is b        # => True

2 in [1,2,3]                     # => True
'cd' in 'abcdef'                 # => True

important Python permet de chaîner les comparaisons ! On peut par exemple définir la condition 10 < age < 20 qui est équivalente à age>10 and age<20.

2.2 Types containers

Les types containers permettent d’instancier des objets contenant plusieurs données, contrairement aux types simples (booléen, entier, flottant, complexe). On distingue fondamentalement 3 catégories de containers, implémentés sous forme de 6 types de base (built-in) Python :

2.2.1 Chaînes de caractères


On définit une chaîne de caractères en la délimitant par des apostrophes ' ou guillemets ", caractères que l’on triple pour une chaîne littérale multi-ligne.

Le type chaîne, sous Python, est immutable. Les chaînes ne sont donc à proprement parler pas modifiables : les opérations de modification entraînent la création de nouvelles chaînes, et le garbage collector Python détruisant automatiquement les anciennes chaînes qui ne sont plus référencées.

Une chaîne étant une séquence ordonnée de caractères, on peut s’y référer par les indices de ces caractères. Les exemples ci-contre illustrent le fait que :

# Définition
metal1 = 'L\'argent' # délimitation par apostrophes
metal2 = "l'or"      #   ou par guillemets

# Concaténation
metal = metal1 + ' et ' + metal2 # => "L'argent et l'or"
type(metal)  # => builtins.str
3.00 + '$'   # => erreur: faire: str(3.00)+'$' => "3.0$"
3.00 * '$'   # => erreur
3 * '$'      # pas d'erreur ! => "$$$"

# Adressage
len(metal)   # => 16
metal[:8]    # idem que  metal[0:8] => "L'argent"
metal[12:]   # idem que  metal[12:len(metal)] => "l'or"

# Modification
metal[9:0] = 'sale' # => erreur (chaînes immutables)
metal = metal[:9] + 'sale' => "L'argent sale"
  # fonctionne, car la var. metal pointe sur une
  # nouvelle donnée, et l'ancienne ("L'argent et l'or")
  # sera supprimée par le garbage collector

# Chaînes multi-ligne (\n dans les 3 prem. expressions !)
str2 = '''chaîne
multi-ligne'''      # => "chaîne\nmulti-ligne"

str2 = """chaîne
multi-ligne"""      # => "chaîne\nmulti-ligne"

str2 = 'chaîne\nmulti-ligne'    # => idem

str3 = 'chaîne \
multi-ligne'  # => "chaîne mono-ligne" (sans \n)

Pour davantage d’information sur les traitements relatifs aux chaînes de caractères, voir le chapitre spécifique plus bas.

2.2.2 Listes


Une liste Python permet de stocker une collection ordonnée (séquence) et dynamiquement modifiable d’éléments hétérogènes (c-à-d. de n’importe quel type, y.c. listes ou dictionnaires imbriqués, fonctions, classes…). Ce type de donnée, qui correspondrait par exemple sous MATLAB/Octave au tableau cellulaire.

Syntaxiquement, on utilise les crochets [elem,elem...] pour définir le contenu d’une liste. Les virgules délimitant chaque élément sont obligatoires (contrairement à MATLAB/Octave).

Pour accéder au contenu d’une liste (adresser ses éléments), on indique également les indices entre crochets [ ] (et non parenthèses comme sous MATLAB/Octave). Comme pour les chaînes :

Méthodes pour ajouter des éléments :

Pour supprimer des éléments :

# Définition
lst = [1,2,'abc','def'] # liste avec diff. types d'élém.
type(lst)    # => type builtins.list (cf. %whos IPython)
type(lst[2]) # => type élément 'abc' => builtins.str

# Adressage et modification
lst[1:3]        # => [2, 'abc']
lst[0,1,3]      # => erreur (énumér. indices impossible)
lst[2:] = 3,4   # écrasement depuis pos. 2 => [1,2,3,4]
v1,v2,v3,v4 = lst # unpacking => v1=1, v2=2, v3=3, v4=4
    # retournerait erreur si nombre de var != nb. élém.
    # il faut dans ce cas procéder comme ci-dessous :
v1, *v, v4 = lst  # => v1=1, v=[2, 'abc'], v4=4

# Ajout ou insertion d'éléments, concaténation de listes
lst[5] = 5        # => erreur "index out of range"
lst.append(5)     # ajout en fin liste=> [1, 2, 3, 4, 5]
    # ou faire:  lst += [5]
lst.insert(0,'a') # insère élém. spécifié à pos. spécif.
                  #   (ici 0) => ['a', 1, 2, 3, 4, 5]
  # rem: append & insert n'insère qu'1 élém. à la fois !
lst.extend(['b','a'])  # => ['a', 1, 2, 3, 4, 5,'b','a']
    # ou concat.: lst=lst+['b','a']  ou lst += ['b','a']
2 * [10, 20]      # => [10,20,10,20] (et non [20,40] !)
[0]*10            # => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

# Suppression d'éléments (selon contenu ou indice)
lst.remove('a')   # 1er élém. 'a'=> [1,2,3,4,5,'b','a']
lst.remove('a')   # occur. 'a' suiv. => [1,2,3,4,5,'b']
del(lst[4:])      # élém. pos. 4 -> fin => [1, 2, 3, 4]
                  #   on aurait pu faire: lst[4:]=[]
lst.pop()         # dern. élém. et le retourne => 4
lst.pop(1)        # élém. pos. 1 et le retourne => 2

# Diverses méthodes et fonctions
lst = [4, 1, 3, 2]
len(lst)          # nombre éléments de la liste => 4
min(lst)          # le plus petit élément => 1
max(lst)          # le plus grand élément => 4
    # min & max utilisable que si collection mêmes types

2 in lst          # test existence élément => True
lst.index(3)      # position 1ère occurrence de 3 => 2
l2 = sorted(lst)  # => [1, 2, 3, 4]
lst.sort()        # modifierait direct. lst => [1,2,3,4]
lst.reverse()     # modifierait direct. lst => [4,3,2,1]

lst.insert(2,['ab','cd']) # => [1, 2, ['ab', 'cd'], 3,4]
lst[2]            # => ['ab', 'cd'] : élém. est liste !
lst[2][1]         # adressage dans liste => 'cd'
lst[2][1][0]      # => 'c'

Le type liste se prête bien à l’implémentation de piles (stacks) :

important Remarque : on serait tenté d’utiliser des listes imbriquées pour manipuler des matrices, par exemple mat_2D = [ [1,2,3], [4,5,6] ], puis accès aux éléments avec mat_2D[1][0] => 4. Mais ce n’est pas du tout efficace ni flexible (p.ex. difficile d’extraire une colonne). On a pour cela bien meilleur temps d’utiliser la bibliothèque NumPy.

Pour itérer les éléments d’une liste, voir la boucle for décrite plus bas.

Pour construire des listes par itération avec une syntaxe très compacte, voir la compréhension de listes, sets et dictionnaires.

Copie de listes

attention Si l’on souhaite dupliquer les données d’une liste l1=[...], il est très important de comprendre qu’avec Python une simple assignation l2=l1 n’effectue pas de copie des données : les 2 variables l1 et l2 référenceront en effet la même liste !

important Pour réellement recopier les données d’une liste, il faut donc prendre des précautions particulières :

l1 = [1,2,3]
l2 = l1      # l2 pointe vers l1, ce qu'on peut vérifier
             #   avec id(l1) et id(l2)
l2[1] = 'a'  # donc on modifie ici données originales !
l2           # => [1, 'a', 3]
l1           # => également [1, 'a', 3] !!!

l1 = [1,2,3]
l2 = l1[:]   # on crée ici par copie un nouvel objet !
l2[1] = 'a'  # donc on agit dans ce cas sur nouvel objet
l2           # => [1, 'a', 3]
l1           # => [1, 2, 3] : données orig. intouchées !

Cette problématique de copie ne concerne pas les types immutables tels que les tuples et frozensets, car il n’y a par définition aucun risque qu’on modifie leurs données.

2.2.3 Tuples

Le tuple Python est l’implémentation en lecture seule de la liste, c’est-à-dire une collection ordonnée non modifiables d’éléments hétérogènes. On parle ainsi liste immutable. Ce type de donnée possède les mêmes méthodes que la liste, à l’exception de celles permettant une modification.

Lorsque l’on définit par énumération les éléments du tuple, ceux-ci doivent être délimités par des virgules. On peut emballer le tout entre parenthèses (elem,elem...), mais ce n’est pas obligatoire.
important Lorsque le tuple ne contient qu’un seul élément, il est nécessaire de le faire suivre d’une virgule.

Pour accéder aux éléments du tuple, on spécifie les indices entre crochets [ ] (comme pour les listes).

# Définition
nb = (10)  ; str = ('abc')  # => resp. entier & chaîne !
tpl= (10,) ; tpl = 10, ; tpl=('abc',) # => tuples

tpl = (1, 'Linux', 2, 'Windows') # on peut omettre ( )
type(tpl)   # => type builtins.tuple (cf. %whos IPython)
len(tpl)    # => 4 (nombre d'éléments)
tpl[1::2]   # => ('Linux', 'Windows')

tpl[3] = 'macOS'     # => erreur (tuple non modifiable)
tpl +=  3, 'macOS'   # possible: crée nouv. objet tuple
'Linux' in tpl        # => True

tpl2= tuple([10,11,12])# copie liste->tuple=> (10,11,12)
tpl3= tuple('hello')  # copie chaine->tuple
                      #    => ('h','e','l','l','o')

lst2= list(tpl2)      # copie tuple->liste => [10,11,12]

Moins flexibles que les listes en raison de leur immutabilité, l’intérêt des tuples réside cependant dans le fait qu’ils occupent moins d’espace mémoire et que leur traitement est plus efficace.

2.2.4 Dictionnaires


Un dictionnaire est une liste modifiable d’éléments hétérogènes indicés par des clés (par opposition aux listes et tuples qui sont indicés par des séquences d’entiers). Le dictionnaire est donc non ordonné. Ce type de donnée Python correspond au tableau associatif (hash) sous Perl, et au map sous MATLAB/Octave.

Syntaxiquement, on utilise les accolades { } pour définir les éléments du dictionnaire, c’est-à-dire les paires clé: valeur (notez bien le séparateur :). Les clés doivent être de type simple immutable (on utilise le plus souvent des chaînes ou des entiers) et uniques (i.e. plusieurs éléments ne peuvent pas partager la même clé). Les valeurs, quant à elles, peuvent être de n’importe quel type (y compris des containers).

# Définition
dic = {'a': 20, 'b': 30}
# ou élément après élément :
  dic={} ; dic['a']=20 ; dic['b']=30
# ou en utilisant le constructeur dict :
  dic = dict(a=20, b=30)
  dic = dict( (('a', 20), ('b', 30)) )
  dic = dict(zip(('a','b'), (20,30)))
type(dic)  # => type builtins.dict (cf. %whos s/IPython)
len(dic)   # => 2 paires clé:valeur

'a' in dic   # test existence clé => True
20  in dic   # => False, car 20 n'est pas une clé !

# Récupérer les valeurs
dic['a']         # => 20
dic['c']         # retourne erreur KeyError
dic.get('a')     # => 20
dic.get('c','erreur blabla')  # => 'erreur blabla'

# Ajouter un élément, modifier val., supprimer élém.
dic[1] = 10      # => {'a': 20, 'b': 30, 1: 10}
dic['b'] = 50    # on remplace la val. 30 par 50
del(dic['b'])    # suppression élément 'b': 50
val=dic.pop('b') # récupère val. & suppr. élém.

# Ajouter plusieurs éléments, fusion de dict.
dic.update({2: 20, 3: 30})
    # => dic = {'a': 20, 3: 30, 2: 20, 'b': 30}

# Objets itérables par boucle for
dic.keys()          # => objet dict_keys(...)
dic.values()        # => objet dict_values(...)
dic.items()         # => objet dict_items(...)
# ... et copie sur liste ou tuple
list(dic.keys())    # => liste ['a', 2, 3, 'b']
tuple(dic.values()) # => tuple (20, 20, 30, 30)

L’accès à une valeur du dictionnaire s’effectue en lui passant entre crochets [ ] la clé correspondante. Les méthodes .keys(), .values() et .items() retournent des objets permettant d’itérer, par une boucle for, respectivement les clés, les valeurs et les paires clés/valeurs.

Les dictionnaires n’étant pas ordonnés (non indicés par des entiers mais par des clés), la technique du slicing n’est bien entendu pas applicable.

important Si l’on veux recopier les données d’un dictionnaire dic1, le problème est le même qu’avec les listes : on ne fera pas dic2=dic1 (les 2 variables se référant dans ce cas au même objet), mais une copie récursive (dans toute la profondeur du dictionnaire) avec dic2 = copy.deepcopy(dic1) (nécessitant l’importation du module copy).

2.2.5 Sets


Le set est un type Python permettant de créer des collections non ordonnées modifiables constituées d’éléments uniques de types immutables. Son grand intérêt réside dans le fait qu’on peut appliquer à ces objets des opérations propres aux ensembles (union, intersection, différence…), d’où le nom de sets (ensembles).

Notez bien que ce ne sont pas des séquences (les éléments ne sont pas ordonnés, donc non accessibles par des indices entiers), mais ils supportent l’itération (l’ordre n’étant cependant pas significatif). C’est un peu des dictionnaires (dont les clés sont aussi uniques) mais sans valeurs.

On crée un set avec la fonction set(iterable) ou en énumérant les éléments entre accolades {elem, elem...}. L’usage des accolades, comme pour les dictionnaires, est cohérent, ces deux types de données contenant des éléments uniques (les données elles-mêmes pour les sets, et les clés pour les dictionnaires).

A la place des opérateurs présentés dans les exemples ci-contre, on peut utiliser des méthodes : .union(iterable) pour |, .intersection(iterable) pour &, .difference(iterable) pour -, .symmetric_difference(iterable) pour ^.

# Définition
set1 = {'a', 'b', 'a', 'c'}     # => {'a', 'c', 'b'}
type(set1)   # => type builtins.set (cf. %whos IPython)
len(set1)    # => 3

set2 = set( ('b','c','d','c') ) # => {'c', 'b', 'd'}
  # seconde façon de définir un set, notez doubles ( ) !

# Ajout ou suppression d'éléments
set3 = set()    # set vide
set3 |= {1, 2}  # ajout plusieurs élém. => {1, 2}
        # identique à: set3 = set3 | {1,2}
        #        ou à: set3.update({1,2})
set3.add(3)     # ajout 1 élém. seul. => {1, 2, 3}
set3.remove(2)  # détruit 1 élém. => {1, 3}
set3.discard(2) # remove, mais pas err. si élém. absent
set3.pop()      # retourne élém. arbitraire et détruit
set3.clear()    # détruit tous les élém. => { }

# Opérations propres aux ensembles
set1 | set2  # union        => {'a', 'b', 'c', 'd'}
set1 & set2  # intersection => {'b', 'c'}
set1 - set2  # différence   => {'a'}
set2 - set1  # différence   => {'d'}
set1 ^ set2  # ou exclusif  => {'a', 'd'}
    # élém. présents dans set1 ou set2 mais pas les deux

# Tests
{1,2} == {2, 1}    # sets identiques => True
1 in {1, 2, 3}     # présence élém. isolé => True
{1,2} <= {1,2,3,4} # présence de tous les élém. => True

important La problématique de copie de sets est la même que pour les listes et dictionnaires. Pour réellement copier les données d’un set s1, on ne fera donc pas s2=s1 (les 2 variables pointant dans ce cas sur le même objet), mais s2=s1.copy() ou s2=set(s1).

2.2.6 Frozensets

Un frozenset est simplement un set immutable, c’est-à-dire non modifiable.

On crée un objet de type frozenset avec la fonction frozenset(iterable).

fset = frozenset( {'x','y','z','x'} ) # ou...
fset = frozenset( ('x','y','z','x') ) # identique
type(fset)   # => type builtins.frozenset

Tout ce que l’on a vu concernant les sets est applicable, à l’exception de ce qui a trait à la modification. Les frozensets occupent moins d’espace en mémoire que les sets, et leur traitement est plus efficace.

2.2.7 Récapitulation des différents types de base

Les différents types de données de base (built-in) offerts par Python sont résumés dans le tableau ci-contre.

Types de base     modifiables     immutables
  simples   aucun booléen
entier
réel flottant
complexe
  containers liste (séquence)
set (ensemble)
dictionnaire (map)
tuple (séquence)
frozenset (ensemble)
chaîne (séquence)

2.2.8 Le slicing

Applicable aux containers indicés par des entiers, c’est à dire les séquences (listes, tuples et chaînes ; donc pas les dictionnaires ni les sets et frozensets), la technique du slicing est une forme avancée de l’indexation permettant d’accéder aux éléments par intervalles (ranges) et tranches (slices). Sa syntaxe générale est : sequ[debut:fin:pas], désignant dans la séquence sequ les éléments d’indices debut ≤ indice ≤ fin-1, avec une périodicité de pas.

On a déjà vu que les éléments d’une séquence sont numérotés positivement de gauche à droite de 0 à len(sequ)-1 (et non de 1 à len(sequ) comme sous MATLAB/Octave ou Fortran).
important Mais ce qu’il faut ajouter ici c’est qu’ils sont aussi numérotés négativement de droite à gauche de -1, -2, -3… jusqu’à -len(sequ) ! Par conséquent, comme on le voit dans les exemples ci-contre, les paramètres debut, fin et pas peuvent aussi prendre des valeurs négatives. S’agissant du pas, une valeur positive signifie un déplacement vers les éléments suivants (à droite), et négative vers les éléments précédents (à gauche).

lst = [1,2,3,4,5,6,7]

lst[-1]         # => 7
lst[-3:]        # => [5, 6, 7]
lst[-1:-3:-1]   # => [7, 6] (et non pas [7, 6, 5] !)
lst[-1:-3]      # => [] car on ne peut pas aller de -1
                #    à -3 avec le pas par défaut de 1
lst[2:-2]       # => [3, 4, 5]
lst[::2]        # => [1, 3, 5, 7]
lst[::-1]       # => [7, 6, 5, 4, 3, 2, 1]
lst[:2]         # => [1, 2]
lst[:-2]        # => [1, 2, 3, 4, 5]

lst[2:-2] = 'a' # remplacement d'éléments
lst             # => [1, 2, 'a', 6, 7]
lst[1:-1] = []  # suppression d'éléments
lst             # => [1, 7]

chaine = 'abcdef'
chaine[::-1]  # du dern. au 1er car. (miroir)=> 'fedcba'
chaine[::-2]  # idem en sautant 1 car. sur 2 => 'fdb'

Il faut encore préciser que chacun de ces 3 paramètres debut, fin et pas peut être omis :

attention Il faut finalement relever que, contrairement à MATLAB/Octave, Python ne permet pas d’adresser des éléments en énumérant leurs indices séparés par des virgules. L’expression lst[0,3,4] retourne donc une erreur.

2.2.9 Les fonctions range et enumerate

La fonction range(debut, fin, pas) crée un objet itérable (notamment utilisable par boucle for) correspondant à la suite de nombre entiers :
debut ≤ nombre ≤ fin-1 si le pas est positif,
debut ≥ nombre ≥ fin+1 si le pas est négatif.
S’ils ne sont pas fournis, les paramètres debut et pas prennent respectivement les valeurs par défaut 0 et 1.

important Pour créer une liste d’entiers à partir de l’objet range, on utilisera la fonction list(objet), et pour créer un tuple, la fonction tuple(objet).

rg = range(5) # => range(0,5)
type(rg)      # => type builtins.range (%whos s/IPython)

list(rg)      # => [0, 1, 2, 3, 4]
tuple(rg)     # => (0, 1, 2, 3, 4)

list(range(-4,10,3)) # => [-4, -1, 2, 5, 8]
list(range(5,0,-1))  # => [5, 4, 3, 2, 1]

python2 Sous Python ≤ 2, la fonction range fabriquait directement une liste, et il existait une fonction xrange (qui a désormais disparu) qui se comportait comme la fonction range actuelle (création d’un itérateur).

La fonction enumerate(sequence, debut) crée un objet itérable retournant l’indice et la valeur des éléments d’une séquence (liste, tuple et chaîne) ou d’un objet iterable. Cette fonction est également très utilisée dans les boucles for.

Si l’on passe le paramètre debut (valeur entière), les indices retournés démarreront à cette valeur et non pas 0.

lst = list(range(3,7)) # => [3, 4, 5, 6]

en0 = enumerate(lst)
type(en0) # => type builtins.enumerate
list(en0) # => [(0, 3), (1, 4), (2, 5), (3, 6)]

en5 = enumerate(lst,5)
list(en5) # => [(5, 3), (6, 4), (7, 5), (8, 6)]

str = 'abc'
ens = enumerate(str)
list(ens) # => [(0, 'a'), (1, 'b'), (2, 'c')]

Sachez encore qu’il existe un module standard itertools permettant de créer différents types d’itérateurs.

2.3 Structures de contrôle

2.3.1 Indentation des blocs de code

attention En Python, l’indentation du code est significative et donc fondamentale !!! C’est elle qui permet de définir les blocs de code, remplaçant en cela les accolades utilisées dans d’autres langages. Cette obligation d’indenter son code pour le structurer entraîne en outre une grande lisibilité et légèreté du code (absence d’accolades, points-virgules…).

important Pour des raisons de portabilité, le “Style Guide for Python Code” recommande d’utiliser 4 <espace> par niveau d’indentation et pas de caractère <tab>. En outre Python v3 ne permet plus de mixer les <espace> et <tab> dans un même bloc. Nous vous recommandons donc vivement de configurer votre éditeur ou votre IDE de façon que l’usage de la touche <tab> du clavier insère 4 caractères <espace> (et non pas 1 caractère <tab> ou 8 <espace>) !

important Notez encore qu’un bloc de code (ceci est notamment valable pour les fonctions, les structures if, for, while…) doit contenir au minimum une instruction. S’il n’en a pas, on peut utiliser l’instruction pass qui n’effectue aucune action (placeholder).

2.3.2 Exécution conditionnelle if - elif - else

L’exemple ci-contre illustre la forme complète de cette structure. Les parties elif... et else... sont facultatives. Pour des tests multiples, on peut bien entendu cascader plusieurs parties elif....

Notez bien la présence du caractère : (double point) précédant le début de chaque bloc !

a, b = 4, 5
if a > b:
    print("%f est supérieur à %f" % (a,b) )
elif a == b:
    print("%f est égal à %f" % (a,b) )
else:
    print("%f est inférieur à %f" % (a,b) )

important Sachez que si, à la place d’une condition, on teste directement un objet :

Mais comme une règle de bonne conduite Python dit qu’il faut programmer les choses explicitement, il est préférable de tester si un objet n’est pas vide en faisant if len(objet)!=0 plutôt que if objet !

Expressions conditionnelles

important Pour les structures if... else... dont les blocs ne contiendraient qu’une instruction, Python propose, comme d’autres langages, une forme compacte tenant sur une seule ligne appelée expressions conditionnelles (ternary selection). Sa syntaxe est :

expressionT if condition else expressionF

expressionT est évalué si la condition est vraie, sinon expressionF.

# L'instruction :
print('Paire')  if  val%2 == 0  else  print('Impaire')

# est une forme abrégée de :
if val%2 == 0:
    print('Paire')
else:
    print('Impaire')

Quelques fonctions logiques

On peut tester une séquence, un ensemble ou un objet iterable avec les fonctions built-in suivantes :

any(range(0,3))    # => True
     # car éléments de valeurs 1 et 2 assimilés à vrai
all(range(0,3))    # => False
     # car élément de valeur 0 assimilé à faux

2.3.3 Boucle for

La boucle for permet d’itérer les valeurs d’une liste, d’un tuple, d’une chaîne ou de tout objet itérable. Comme dans les autres structures de contrôle, le caractère : (double point) définit le début du bloc d’instruction contrôlé par for.

Pour itérer sur une suite de nombres entiers, on utilise souvent la fonction range (objet itérable) présentée plus haut.

Ci-contre, notez aussi l’utilisation de la fonction enumerate retournant donc un objet permettant d’itérer sur l’indice et la valeur d’une séquence, d’où les 2 variables qui suivent le mot-clé for !

De même, la méthode dictionnaire.items() retourne un objet permettant d’itérer sur la clé et la valeur de chaque élément d’un dictionnaire. Les méthodes .keys() et .values() retournent quant à elles respectivement les clés et les valeurs du dictionnaire.


important Après le bloc d’instruction subordonné au for, on pourrait (mais cela n’est pas courant !) ajouter une clause else suivie d’un bloc d’instructions, ces dernières s’exécutant 1 fois lorsque l’on a terminé d’itérer.

# Sur listes ou tuples
lst = [10, 20, 30]
for n in lst:
    print(n, end=' ')   # => 10 20 30

for index in range(len(lst)):
    print(index, lst[index])
    # => affiche:  0 10
    #              1 20
    #              3 30

for index,val in enumerate(lst):
    print(index, val)
    # => même affichage que ci-dessus

# Sur chaînes
voyelles = 'aeiouy'
for car in 'chaine de caracteres':
    if car not in voyelles:
        print(car, end='')
        # => affiche les consonnes: chn d crctrs

# Sur dictionnaires
carres = {}
for n in range(1,4):
    carres[n] = n**2    # => {1: 1, 2: 4, 3: 9}

for k in carres:  # itère par défaut sur la clé !
  # identique à: for k in carres.keys():
    print(k, end=' ')   # => 1 2 3

for n in sorted(carres):
  # identique à: for n in sorted(carres.keys()):
    print("Carré de %u = %u" % (n,carres[n]))
    # => affiche:  Carré de 1 = 1
    #              Carré de 2 = 4
    #              Carré de 3 = 9

for cle,val in carres.items():
    print('Clé: %s, Valeur: %s' % (cle, val))
    # => affiche:  Clé: 1, Valeur: 1
    #              Clé: 2, Valeur: 4
    #              Clé: 3, Valeur: 9

Compréhension de listes, sets et dictionnaires

important Lorsqu’il s’agit de construire un container de type liste, set ou dictionnaire à l’aide d’une boucle for assortie éventuellement d’une condition, Python offre une construction compacte appelée comprehension expression. Sa syntaxe est :

liste = [ expression for expr in iterable if cond ]

set = { expression for expr in iterable if cond }

dict = { expr1:expr2 for expr in iterable if cond }

S’agissant d’une liste ou d’un set, l’expression, évaluée à chaque itération de la boucle, constitue la valeur de l’élément inséré dans la liste ou le set.

Concernant le dictionnaire dict, les expressions expr1 et expr2 constituent respectivement les clés et valeurs des paires insérées.

Le test if cond est facultatif. Il peut aussi prendre la forme d’une expression conditionelle telle que présentée plus haut :

[exprT if cond else exprF for expr in iterable]

# L'expression ci-dessous fabrique la liste des carrés
# des nombres pairs de 1 à 10 => [4, 16, 36, 64, 100]
carres = [ nb*nb  for nb in range(1,11) if nb%2==0 ]

# ce qui est équivalent à :
carres = []
for nb in range(1,11):
    if nb%2 == 0:
        carres.append(nb*nb)

# L'expression ci-dessous retourne, dans l'ordre alpha-
# bétique, la liste des car. utilisés dans une chaîne
sorted({ car for car in 'abracadabra' })
    # => ['a', 'b', 'c', 'd', 'r']

# L'expression ci-dessous récupère, dans le dictionnaire
# dic, tous les éléments dont la valeur est supérieure
# à 10, et les insère dans le nouveau dictionnaire sup10
dic = { 'a': 12.50, 'b': 3.50, 'c': 11.00, 'd': 6.00 }
sup10 = { cle:val for cle,val in dic.items() if val>10 }

# L'expression ci-dessous parcourt les entiers de 1 à 9
# et affiche les valeurs impaires, sinon 0
[ x if x%2 else 0   for x in range(1,10) ]
    # => [1, 0, 3, 0, 5, 0, 7, 0, 9]

2.3.4 Boucle while

La boucle while permet d’exécuter un bloc d’instruction aussi longtemps qu’une condition (expression logique) est vraie.

Notez aussi la présence du caractère : (double point) définissant le début du bloc d’instruction contrôlé par while.

nb = 1 ; stop = 5
# Affiche le carré des nombres de nb à stop
while nb <= stop:
    print(nb, nb**2)
    nb += 1

Comme pour la boucle for, après le bloc d’instruction subordonné à while, on pourrait aussi ajouter une clause else suivie d’un bloc d’instructions, ces dernières s’exécutant 1 fois lorsque la condition sera fausse.

2.3.5 Instructions continue et break

Dans une boucle for ou une boucle while :

# Affiche les nombres impairs et leur carré jusqu'à ce
# que le carré atteigne 25 => 1  1 , 3  9 , 5  25
for n in range(0, 10):
    if (n % 2) == 0:  # nombre pair
        continue      # => re-boucler
    carre = n**2
    if carre > 25:
        break         # => sortir de la boucle
    print(n, carre)

2.4 Fonctions, modules, packages, scripts

2.4.1 Fonctions

De façon générale, on implémente une fonction lorsqu’un ensemble d’instructions est susceptible d’être utilisé plusieurs fois dans un programme. Cette décomposition en petites unités conduit à du code plus compact, plus lisible et plus efficace.

L’exemple ci-contre illustre les principes de base de définition d’une fonction en Python :

def somProd(n1, n2):
    """Fonction calculant somme et produit de n1 et n2
    Résultat retourné dans un tuple (somme, produit)"""
    return (n1+n2, n1*n2)


help(somProd)  # => affiche :
  **somProd**(n1, n2)
    Fonction calculant somme et produit de n1 et n2
    Résultat retourné dans un tuple (somme, produit)

somProd(3,10)  # => (13, 30)
somProd()  # => erreur "somProd() takes exactly 2 args"
           # et même erreur si on passe 1 ou >2 args.

# Une fonction étant un objet, on peut l'assigner
# à une variable, puis utiliser celle-ci comme un
# "alias" de la fonction !
sp = somProd
sp(3,10)   # => (13, 30)

S’agissant du nom de la fonction, il est de coutume, en Python, de le faire débuter par un caractère minuscule. En outre s’il est composé de plusieurs mots, on concatène ceux-ci et les faisant débuter chacun par une majuscule. Exemple : uneFonctionBienNommee.

Lors de l’appel à la fonction, il est aussi possible de passer les paramètres de façon nommée avec paramètre=valeur. Dans ce cas, l’ordre dans lequel on passe ces paramètres n’est pas significatif !

On peut en outre définir, dans la déclaration def, des paramètres optionnels. Si l’argument n’est pas fourni lors de l’appel de la fonction, c’est la valeur par défaut indiquées dans la définition qui sera utilisée par la fonction.

def fct(p1, p2=9, p3='abc'):
    # 1 param. obligatoire, et 2 param. optionnels
    return (p1, p2, p3)

print(fct())         # => erreur (1 param. oblig.)
print(fct(1))        # => (1, 9, 'abc')
print(fct(1, 2))     # => (1, 2, 'abc')
print(fct(1, 2, 3))  # => (1, 2, 3)
print(fct(p3=3, p1='xyz')) # => ('xyz', 9, 3)

Si le nombre de paramètres n’est pas fixé d’avance, on peut utiliser les techniques suivantes :

def fct1(*params_tpl):
    print(params_tpl)

fct1(1, 'a')         # => (1, 'a')


def fct2(**params_dic):
    print(params_dic)

fct2(p1=1, p2='a')   # => {'p2': 'a', 'p1': 1}

On peut finalement combiner ces différentes techniques de définition de paramètres (paramètres optionnels, tuple, dictionnaire) ! Par exemple la fonction fct(n1, n2=4, *autres) a 1 paramètres obligatoire, 1 paramètre facultatif avec valeur par défaut, et 0 ou plusieurs paramètres positionnels supplémentaires facultatifs.

attention Il est important de noter que sous Python les objets transmis aux fonctions sont passés par référence (adresses vers ces objets), contrairement à MATLAB/Octave (passage par valeur) ! S’ils sont modifiés par les fonctions, ces objets le seront donc également dans le programme appelant !

Il y a cependant une exception à cette règle qui concerne les variables de type non modifiable (immutables), celles-ci étant alors passées par valeur (copie des données). Cela concerne donc les types simples (entier, flottant, complexe) et les containers de type tuples, chaînes et frozensets.

def ajoutElem(liste, elem):
    liste.append(elem)
    elem = 'xyz'
    # notez que cette fonction n'a pas de return !

liste=['un']
elem='deux'
ajoutElem(liste, elem)
print(liste) # => ['un', 'deux'] : liste a été modifiée!
print(elem)  # => 'deux' : chaîne n'a PAS été modifiée

Portée des variables (et autres objets)

La portée est le périmètre dans lequel un nom (de variable, fonction…) est connu (visible) et utilisable. Sous Python, cela est lié au concept d’espaces de noms (namespaces) où il faut distinguer :

La fonction dir() retourne la liste des noms connus dans l’espace de noms courant (et dir(objet) la liste des attributs associés à l’objet spécifié).

important La recherche d’un nom débute dans l’espace de noms le plus intérieur, puis s’étend jusqu’à l’espace de nom global. C’est ainsi que :

# Exemple 1 ____________________________________________

def afficheVar():     # fonction sans param. d'entrée
    var2 = 999        # variable locale
    print('var1 =', var1, '\nvar2 =', var2)


var1 = 111 ; var2 = 222    # variables globales
afficheVar()  # => affiche :
  # var1 = 111 => variable globale vue par la fct
  # var2 = 999 => var. locale masquant var. globale




# Exemple 2 ____________________________________________

def incremCompt():   # fonction sans param. d'entrée
    global compteur  # définition var. globale
    if 'compteur' not in globals(): # test exist. var.
        compteur = 0 # initialisation du compteur
    compteur += 1
    print('Appelé', compteur, 'fois')


incremCompt() # => affiche: Appelé 1 fois
incremCompt() # => affiche: Appelé 2 fois
incremCompt() # => affiche: Appelé 3 fois

Lambda fonctions

important Pour les fonctions donc le corps ne contiendrait qu’une seule instruction return expression, Python offre la possibilité de définir une fonction anonyme aussi appelée lambda fonction (ou lambda expression). Sa syntaxe est :

lambda arguments... : expression

Dans l’exemple ci-contre, nous l’assignons la lambda fonction à une variable pour pouvoir l’utiliser comme une fonction classique. Dans la pratique, les lambda fonctions sont utiles pour définir une fonction simple comme argument à une autre fonction, pour retourner des valeurs, etc…

# La lambda fonction ci-dessous :
somProd = lambda n1, n2: (n1+n2, n1*n2)

# est équivalente à :
def somProd(n1, n2):
    return (n1+n2, n1*n2)

# Dans les 2 cas :
somProd(3, 10)    # => (13, 30)

2.4.2 Modules

Utilisation de modules

Un module Python (parfois appelé bibliothèque ou librairie) est un fichier rassemblant des fonctions et classes relatives à un certain domaine. On implémente un module lorsque ces objets sont susceptibles d’être utilisés par plusieurs programmes.

Pour avoir accès aux fonctions d’un module existant, il faut charger le module avec la commande import, ce qui peut se faire de différentes manières, notamment :

  1. from module import * : on obtient l’accès direct à l’ensemble des fonctions du module indiqué sans devoir les préfixer par le nom du module
  2. from module import fct1, fct2... : on ne souhaite l’accès qu’aux fonctions fct1, fct2… spécifiées
  3. import module1, module2... : toutes les fonctions de(s) module(s) spécifié(s) seront accessibles, mais seulement en les préfixant du nom du module
  4. import module as nomLocal : toutes les fonctions du module sont accessible en les préfixant du nomLocal que l’on s’est défini

Les méthodes (C) et (D) sont les plus pratiquées. La technique (A) ne devrait en principe pas être utilisée, car elle présente le risque d’écrasement d’objets si les différents modules chargés et/ou votre programme implémentent des objets de noms identiques. Elle rend en outre le code moins lisible (on ne voit pas d’où proviennent les fonctions utilisées).

Explication technique : quand on importe un module avec (C) ou (D), un “espace de nom” spécifique (que l’on peut examiner avec dir(module)) est créé pour tous les objets du module, puis un objet de module est créé pour fournir l’accès (avec la notation module.fonction()) à cet espace de nom.

On obtient la liste des modules couramment chargés avec l’attribut sys.modules.

Les fonctions et classes Python de base (built-in) sont définis dans un module préchargé nommé builtins.

S’agissant de l’installation de modules et packages ne faisant pas partie de la librairie standard, voyez ce chapitre.

# Admettons qu'on doive utiliser la constante 'pi' et la
# fonction 'sin', tous deux définis dans le module 'math'
# (Rem: %who et %whos ci-dessous sont propre à IPython)

# A)
from math import *
%who     # ou %whos => on voit toutes fcts importées !
dir()    # => objets dans namespace, notamment ces fcts
sin(pi/2)  # => 1.0
cos(pi)    # => -1.0

# B)
from math import pi, sin
%who     # ou %whos => seules 'pi', 'sin' accessibles
dir()    # => objets dans namespace, notamment ces fcts
sin(pi/2)  # => 1.0
cos(pi)    # => erreur "name 'cos' is not defined"

# C)
import math
%who              # ou %whos => module 'math' importé
math.<tab>        # => liste les fonctions du module
dir(math)         # => objets dans namespace
help(math)        # => affiche l'aide sur ces fonctions
help(math.sin)    # => aide sur la fct spécifiée (sin)
math.sin(math.pi/2) # => 1.0
cos(pi)           # => erreur (non préfixés par module)
math.cos(math.pi) # => -1.0

# D)
import math as mt
%who     # (ou %whos) => module math importé s/nom mt
mt.<tab>          # => liste les fonctions du module
dir(mt)           # => objets dans namespace
help(mt)          # => affiche l'aide sur ces fonctions
mt.sin(mt.pi/2)     # => 1.0
math.sin(math.pi/2) # => erreur "name math not defined"
mt.cos(mt.pi)       # => -1.0

Écriture de modules

Créer un module revient simplement a créer un fichier nommé module.py dans lequel seront définies les différents objets (fonctions, classes…) du module. Le nom du module est en général défini en caractères minuscules seulement (a-z, avec éventuellement des _).

Outre les fonctions, on peut également ajouter dans le module des instructions à exécution immédiate, celles-ci étant alors exécutées au moment de l’import. important Si l’on souhaite qu’elles ne soient exécutées que lorsqu’on exécute le module en tant que script, on les définira dans un bloc précédé de if __name__ == '__main__':

Lors d’un import, Python commence par rechercher le module spécifié dans le répertoire courant, puis dans le(s) répertoire(s) défini(s) par la variable d’environnement PYTHONPATH (si celle-ci est définie, par exemple par votre prologue ~/.profile), puis finalement dans les répertoires systèmes des modules Python (/usr/lib/python<version>). La liste de tous ces répertoires (chemins de recherche) est donnée par l’attribut sys.path. Python considère, dans l’ordre, les extensions suivantes :

# Fichier mon_module.py ________________________________

def carre(nb):
    return nb*nb

def cube(nb):
    return nb*nb*nb

if ___name___ == '___main___':
    # module exécuté en tant que script
    print('Exemple de la fonction carre()')
    print('  carre(4) => ', carre(4))
    print('Exemple de la fonction cube()')
    print('  cube(3) => ', cube(3))


# Utilisation du module ________________________________

import mon_module      # => n'exécute pas code
mon_module.carre(3)    # => 9
mon_module.cube(2)     # => 8


# Exécution du module en tant que script _______________

$ python mon_module.py   # => affiche ce qui suit :
  Exemple de la fonction carre()
    carre(4) =>  16
  Exemple de la fonction cube()
    cube(3) =>  27

important Lorsque l’on développe interactivement, Python ne charge le module que la première fois qu’on l’importe. Si l’on veut recharger un module que l’on vient de modifier, il faut faire : imp.reload(module). Il faut cependant être attentif au fait que les objets créés avant le rechargement du module ne sont pas mis à jour par cette opération !

2.4.3 Packages

Un package (paquetage) permet de réunir plusieurs modules sous un seul nom et pouvant ainsi être chargés par une seule instruction import package. C’est donc un ensemble de modules physiquement rassemblés dans un répertoire ou une arborescence. Nous n’entrerons ici pas davantage dans les détails d’implémentation d’un package.

2.4.4 Scripts

Un script (ou programme) Python est un fichier de code Python que l’on peut exécuter dans un interpréteur Python. Son nom se termine en principe par l’extension .py.

On a déjà dit quelques mots sur les 2 lignes d’en-têtes d’un script (définition de l’interpréteur, encodage des caractères). Suivent alors les instructions d’importation des modules utilisés. Puis le script se poursuit par la définition d’éventuelles fonctions. On trouve finalement le corps principal du programme, entité connue par l’interpréteur Python sous le nom __main__

On peut exécuter un script nommé script.py de différentes manières :

# Fichier monScript.py _________________________________

#!/usr/bin/env python
# -*- coding: utf-­8 ­-*-

import sys

def pause(prompt):  # exemple de fonction dans script
    print()
    input(prompt)

print('- nom de fichier du script :', sys.argv[0])
print('- invoqué avec', len(sys.argv)-1, \
         'arguments :', sys.argv[1:])

pause('Frapper <enter> pour refermer la fenêtre ')
# Ligne ci-dessus: pour voir qqch si on lance le
# script par double-clic depuis un explorateur



# Exécution du script __________________________________

$ python monScript.py un deux  # => affiche :

- nom de fichier du script : monScript.py
- invoqué avec 2 arguments : ['un', 'deux']

Frapper <enter> pour refermer la fenêtre

Comme on le voit dans l’exemple ci-dessus, on peut récupérer sur l’attribut sys.argv le nom du script et les arguments qui lui ont été passés à l’exécution. Mais sachez qu’il existe des modules spécifiquement dédiés au parsing d’arguments (notamment argparse) qui vous seront bien utiles si vous souhaitez invoquer le script à la façon des commandes Unix (avec des arguments du type : -o, –option, –option=valeur, etc…).

2.5 Opérations sur les chaînes

Après avoir brièvement présenté plus haut comment on définit et manipule des chaînes de caractères sous Python, nous présentons ici plus en détail d’autres traitements possibles.

2.5.1 Formatage

Opérateur de conversion %

Le formatage permet de composer des chaînes de caractères incorporant des données de types entier, flottant et chaîne. Cela correspond, sous C ou MATLAB/Octave, à l’usage de la fonction sprintf. Sous Python, on utilise la syntaxe suivante :

'chaîne avec spécifications de conversion %s %d %o %x %f %e' % ( valeurs, variables ou expressions )

À la suite de la chaîne et de l’opérateur de conversion %, les objets à insérer (valeurs, variables, expressions) sont passés entre parenthèses, c’est-à-dire sous forme de tuple. Les valeurs seront alors insérées dans la chaîne aux emplacements balisés par des spécifications de conversion qui doivent exactement correspondre, en nombre et type, avec les éléments insérés !

Les principales spécifications de conversion sont les suivantes (issues du langage C) :

data = [ 100, 'stylos', 2.5,
         20, 'plumes', 25,
         2, 'calculettes', 105 ]
n = 0
while n < len(data):
    print('  %4d  %-12s  %7.2f ' %
           ( data[n], data[n+1], data[n+2] ) )
    n += 3

# L'exécution du code ci-dessus affiche :
   100  stylos           2.50
    20  plumes          25.00
     2  calculettes    105.00

On peut en outre intercaler, entre l’opérateur % et les codes de conversion s d o x f et e :

Le nombre doit être entier pour les spécifications %s %d %o et %x. Il peut être réel (sans exposant) pour %f et %e, la valeur après le point décimal indiquant alors la précision d’affichage, c’est-à-dire le nombre de chiffres après la virgule. Par exemple %7.2f insère, dans un champ de 7 caractères, le nombre formaté avec 2 chiffres après la virgule. Sans indication de nombre, la largeur du champ est dynamique, correspondant au nombre de chiffres effectif de l’entier inséré ou au nombre de caractères de la chaîne insérée. S’agissant d’un flottant, il sera alors affiché avec une précision de 6 chiffres après la virgule.

Méthode de formatage format

Sous Python v3, le type chaîne s’enrichit d’une nouvelle méthode .format :

'chaîne avec indicateurs de format {}'.format( valeurs, clé=valeur, variables, expressions )

Définis dans la chaîne entre accolades { }, les indicateurs de format permettent de spécifier notamment :

S’agissant des données à formater, si l’on passe une variable référant une séquence, on peut faire un unpack des valeurs en faisant précéder le nom de variable du caractère *. Dans le cas où la variable se réfère à un dictionnaire, on la précédera de **.

Pour davantage de détails, voyez la documentation Python.

# Pour obtenir le même affichage que l'ex. ci-dessus :
...
    print('  {:4d}  {:<12s}  {:7.2f} '.format(
           data[n], data[n+1], data[n+2]) )
...

# Passage par position :
'{} {} {}'.format(11, 'abc', 3.5)   # => '11 abc 3.5'
'{0}{1}{0}'.format('abra', 'cad')   # => 'abracadabra'
'{1:.4f} {0}'.format('abc', 3.5)    # => '3.5000 abc'

# Passage d'un objet tuple :
coord=(12.3, 56.7)
'{0[0]}, {0[1]}'.format(coord)       # => '12.3, 56.7'
'X= {:.2f} Y= {:.2f}'.format(*coord)
                            # => 'X= 12.30 Y= 56.70'

# Passage par nom, ou passage d'un objet dictionnaire :
'Coord: {x:.2f}, {y:.2f}'.format(y=56.7, x=12.3)
                            # => 'Coord: 12.30, 56.70'
dic = {'y': 56.7, 'x': 12.3}
'Coord: {x:.2f}, {y:.2f}'.format(**dic)
                            # => 'Coord: 12.30, 56.70'

Template de chaîne

python2 Sous Python v2 (depuis 2.4), le module string offrait une méthode .Template() permettant de définir des modèles de chaînes puis en faire usage par un mécanisme de substitution. Mais cette technique, bien que toujours disponible sous Python v3, n’est plus tellement utile, étant donné que la méthode .format(), avec le passage par nom, fait la même chose sans nécessiter de module spécifique.

from string import Template

coords = [ (11, 22), (33, 44) ]
tpl = Template('X = $x, Y = $y')

for point in coords:
    print( tpl.substitute(x=point[0], y=point[1]) )
# => affiche :
X = 11, Y = 22
X = 33, Y = 44

2.5.2 Fonctions et méthodes de chaînes

Quelques fonctions :

Rappelons que + et * ainsi que les opérateurs de comparaison (== < > etc…) peuvent être utilisés sur les chaînes.

len('123')       # => 3 caractères
str(123.456)     # => '123.456'

[ord(car) for car in 'abcdef']
   # => [97, 98, 99, 100, 101, 102]

[chr(code) for code in range(97,103)]
   # => ['a', 'b', 'c', 'd', 'e', 'f']

min('abcABC')    # => 'A'
max('abcABC')    # => 'c'
ascii('Élève')   # => '\\xc9l\\xe8ve'

'oh' + 2*'là'            # => 'ohlàlà'
'Ceci'[0:2]=='Cela'[0:2] # => True
'Ceci' > 'Cela'          # => False

def isMinuscule(ch):
    if 'a' <= ch <= 'z': # condition double !
        return True
    else:
        return False
isMinuscule('e')  # => True
isMinuscule('E')  # => False

Dans les méthodes ci-après, les paramètres debut et fin sont optionnels. Ils permettent de limiter la portée de la méthode sur la portion de chaîne comprise entre les indices debut et fin.

Pour plus de précisions sur ces méthodes, voir help(str.methode).

# Recherche et décompte
st = '123 abc 456 abc 789'
st.find('abc')      # ou st.index('abc')   => 4
st.find('abc',5)    # ou st.index('abc',5) => 12
st.rfind('abc')     # ou st.rindex('abc')  => 12
st.find('xyz')      # => -1
st.index('xyz')     # => erreur "substring not found"

st.count('abc')     # => 2 occurences
st.count('a',0,4)   # => 0 occurences
'abc' in st         # => True
'Oui'.lower().startswith('o') # => True
'fichier.py'.endswith('.py')  # => True

# Substitution
st.replace('abc','xxx',1) # => '123 xxx 456 abc 789'

table=str.maketrans('àéè', 'aee') # table de substit.
'accentués àèé'.translate(table)  # => 'accentues aee'

# Nettoyage
' * bla * '.strip()       # => '* bla *'
'  bla  '.rstrip()        # => '  bla'
'* -bla*--'.strip(' *-')  # => 'bla'
'> commande'.lstrip('> ') # => 'commande'

# Découpage
'Une p\'tit phrase'.split() #=> ['Une',"p'tit",'phrase']
    # <espace>, <tab>, <newline>
'2013-11-08'.split('-') # => ['2013', '11', '08']

'''Parag.
multi-
lignes'''.splitlines() # => ['Parag.','multi-','lignes']

# Concaténation
mots = ['Une', 'petite', 'phrase']

concat = ''
for mot in mots:
    concat += mot  # => 'Unepetitephrase'

''.join(mots)   # => 'Unepetitephrase'
' '.join(mots)  # => 'Une petite phrase'

# Changement de casse
'Attention'.upper()       # => 'ATTENTION'
'Oui'[0].lower() == 'o'   # => True
'Inv. Maj/Min'.swapcase() # => 'iNV. mAJ/mIN'
'une phrase'.capitalize() # => 'Une phrase'
'un titre'.title()        # => 'Un Titre'

# Alignement/justification
'Bla'.ljust(6)            # => 'Bla   '
'123'.rjust(6,'0')        # => '000123'
' xxxx '.center(14,'-')   # => '---- xxxx ----'

En outre des méthodes booléennes permettent de tester les caractères d’une chaîne : .isalnum() (alphanumérique), .isalpha() (alphabétique), .isdecimal(), .isdigit() et .isnumeric() (numérique).

2.5.3 Expressions régulières

chantier Rédaction de ce chapitre en cours, merci de patienter !

2.6 Manipulation de fichiers

Nous décrivons ici les techniques standards de manipulation de fichiers, c’est-à-dire les méthodes et attributs de l’objet-fichier. Sachez que d’autres modules offre des méthodes spécifiques de lecture/écriture de fichiers qui peuvent être utiles, notamment :

2.6.1 Ouverture et fermeture

Pour manipuler un fichier, il faut d’abord l’ouvrir, ce qui se fait avec le constructeur open dont les paramètres principaux sont les suivants :

fd = open(fichier, mode, buffering, encoding=encodage)

fd = open('fich.txt', 'w')
    # ouverture nouveau fichier en écriture

type(fd)       # => io.TextIOWrapper
print(fd)      # => affiche :
  <... name='fich.txt' mode='w' encoding='UTF-8'>
help(fd)       # => méthodes associée à objet-fichier
fd.<tab>       # => ~idem sous IPython
fd.closed      # => False
fd.name        # => 'fich.txt'
fd.mode        # => 'w'
fd.encoding    # => 'UTF-8'
fd.readable()  # => False (serait True si 'r')


# On doit le cas échéant gérer soi-même les
# erreurs d'ouverture, p.ex. ici avec l'implé-
# mentation de notre propre fonction 'error' :

import sys
def error(msg): # fct d'affich. message & exit
    sys.stderr.write("Error: %s\n" % msg)
    sys.exit('Aborting...')

try:
    fd = open('inexistant.txt', 'r')
except IOError as err:
    error(err.strerror)  # erreur => avorter
# ... suite du programme si OK

# Si fichier inexistant => cela affichera :
  Error: No such file or directory
  An exception has occurred, use %tb to
  see the full traceback.
  SystemExit: Aborting...

Les modes d’accès sont les suivants :

et l’on peut ajouter les codes suivants :

La fermeture du fichier s’effectue logiquement avec fd.close(). En cas d’oubli, tous les fichiers ouverts sont automatiquement fermés à la sortie du programme.

# ... suite de l'exemple ci-dessus :
fd.close()   # fermeture du fichier
fd.closed    # => True

2.6.2 Lecture

Plusieurs méthodes peuvent être utilisées, selon que l’on souhaite lire le fichier :

  1. d’une traite
  2. par paquets d’un certain nombre de caractères
  3. ligne après ligne

Dans les exemples qui suivent, on utilisera le fichier essai.txt contenant les 3 lignes suivantes :

   1ère ligne
   2e ligne
   fin

A) Pour la lecture d’une traite en mémoire (convenant à des fichiers pas trop volumineux !), on dispose de deux méthodes :

# Ouverture
fd = open('essai.txt', 'r')

# Lecture intégrale sur une chaîne
chaine = fd.read()
  # => chaine = '1ère ligne\n2e ligne\nfin\n'

fd.seek(0)  # repositionnement au début fichier
  # on aurait pu faire un close puis un open

# Seconde lecture intégrale, sur une liste cette fois
liste = fd.readlines()
  # => liste = ['1ère ligne\n', '2e ligne\n', 'fin\n']

# Fermeture
fd.close()


# Les 3 instruction open, read/readline, close
# pourraient être réunies en une seule, respectivement :

chaine = open('essai.txt', 'r').read()
liste  = open('essai.txt', 'r').readlines()

# et c'est alors le garbage collector Python qui
# refermera les fichiers !

B) Si le fichier est très volumineux, la lecture d’une seule traite n’est pas appropriée (saturation mémoire). On peut alors utiliser la méthode .read(nbcar) de lecture par paquets de nbcar caractères.

La taille de paquet de l’exemple ci-contre est bien entendu trop petite ; ce n’est que pour les besoins de la démonstration.

fd = open('essai.txt', 'r') # ouverture
chaine = ''
while True:
    paquet = fd.read(10)    # paquet 10 car.
    if paquet:    # paquet non vide
        chaine += paquet
    else:  # paquet vide => fin du fichier
        break
fd.close()                  # fermeture
# => chaine = '1ère ligne\n2e ligne\nfin\n'

C) Une lecture ligne par ligne peut s’avérer judicieuse si l’on veut analyser chaque ligne au fur et à mesure de la lecture, ou que le fichier est très volumineux (i.e. qu’on ne veut/peut pas le charger en mémoire).

Dans tous les exemples ci-contre, on illustre leur fonctionnement en affichant les lignes au cours de la lecture, raison pour laquelle on supprime le \n terminant chaque ligne lue avec la méthode de chaîne .rstrip().

# Solution 1
fd = open('essai.txt', 'r')
while True:                # boucle sans fin
    ligne = fd.readline()
    if not ligne: break    # sortie boucle
       # identique à:  if len(ligne)==0: break
    print(ligne.rstrip())
fd.close()

# Solution 2, plus légère
fd = open('essai.txt', 'r')
for ligne in fd.readlines():
    print(ligne.rstrip())
fd.close()

# Solution 3, plus élégante
fd = open('essai.txt', 'r')
for ligne in fd:
    print(ligne.rstrip())
fd.close()

# Solution 3bis, encore plus compacte
with open('essai.txt', 'r') as fd:
    for ligne in fd:
        print(ligne.rstrip())

2.6.3 Écriture ou ajout

En premier lieu il faut rappeler où débutera l’écriture après l’ouverture du fichier. Pour cela, il faut distinguer les différents modes d’accès :
'w' : l’écriture débute au début du fichier
'a' : l’écriture débute à la fin du fichier
'r+' : l’écriture s’effectue à la position courante suite à d’éventuelles opérations de lecture ou de positionnement

Pour garantir de bonnes performances, l’écriture est par défaut bufferisée en mémoire. Cette mémoire tampon est bien entendu entièrement déversée dans le fichier lorsqu’on referme celui-ci avec fd.close(). Mais sachez que l’on peut forcer à tout instant l’écriture du buffer avec fd.flush().

A) La méthode .write(chaîne) écrit dans le fichier la chaîne spécifiée. Elle n’envoie pas de newline après la chaîne, donc on est appelé à écrire soi-même des '\n' là où l’on veut des sauts de ligne.

liste = ['1ère ligne', '2e ligne', 'fin']
  # ici éléments non terminés par \n

fd = open('ecrit.txt', 'w')
for ligne in liste:
    fd.write(ligne + '\n') # on ajoute les \n
fd.close()

B) La méthode .writelines(sequence ou ensemble) écrit dans le fichier une séquence (tuple ou liste) ou un ensemble (set ou frozenset) contenant exclusivement des chaînes. Tous les éléments sont écrits de façon concaténée. Tout comme .write, elle n’envoie pas non plus de newline. Si l’on veut des sauts de ligne après chaque élément, le caractère '\n' devrait être inclu à la fin de chaque élément. Si ce n’est pas le cas, on peut utiliser print (voir ci-après).

liste = ['1ère ligne\n', '2e ligne\n', 'fin\n']
  # les éléments doivent se terminer
  # par \n si l'on veut des sauts de ligne

fd = open('ecrit.txt', 'w')
fd.writelines(liste)
fd.close()

C) La fonction print(...), en lui passant l’argument file = fd, peut aussi être utilisée pour écrire dans un fichier ! Elle se comporte de façon standard, donc envoie par défaut un newline après l’écriture, à moins d’ajouter le paramètre end = '' (chaîne vide).

liste = ['1ère ligne', '2e ligne', 'fin']
  # ici éléments non terminés par \n

fd = open('ecrit.txt', 'w')
for ligne in liste:
    print(ligne, file=fd)
fd.close()

On montre ci-contre comment on peut simplifier la technique B) en faisant usage de l’instruction with qui nous dispense de fermer le fichier. Mais on pourrait simplifier de façon analogue les techniques A) et C) !

with open('ecrit.txt', 'w') as fd:
    fd.writelines(liste)

2.6.4 Autres méthodes sur les fichiers

Les méthodes de fichiers suivantes sont encore intéressantes :

attention Sous Python 3.2 .seek semble bugué lorsque l’on utilise l’option depuis = 1 ou 2 sur des fichiers ouverts en mode texte. Le problème ne se pose pas si on les ouvre en binaire, mais on ne manipule alors plus des caractères Unicode mais des bytes.

fd = open('essai.txt', 'r')
fd.tell()     # => 0 (position à l'ouverture)

fd.seek(6)    # aller au 6e car. depuis déb. fichier
fd.read(5)    # => lecture 'ligne'
fd.tell()     # => 11 (6 + 5)

fd.seek(0,2)  # aller à la fin du fichier
fd.tell()     # => 25 (nb de car. total fichier)

fd.seek(-4,2) # aller au 4e car avant fin fichier
fd.read(3)    # => lecture 'fin'

fd.seek(0)    # "rewind" au début du fichier
fd.tell()     # => 0

fd.close()

2.6.5 Sérialisation et stockage d’objets avec le module pickle

Si vous souhaitez sérialiser des objets et les stocker sur fichier, penchez-vous sur le module standard pickle.

chantier Rédaction de ce chapitre en cours, merci de patienter !

2.7 Quelques fonctions built-in

Les fonctions built-in sont des fonctions de base définies dans le module builtins (anciennement nommé __builtin__ sous Python v2). Ce module étant préchargé (contrairement aux autres modules de la librairie standard), elles sont directement utilisables sans devoir importer de modules. Nous nous contentons de présenter ici les plus courantes qui n’auraient pas été présentées plus haut. Pour la liste complète, voyez la documentation Python.

a, b = 2, 3
eval('a * b')         # => retourne 6

exec("print(a, '*', b, '=>', end='') ; print(a * b)")
         # => affiche:  2 * 3 => 6

chantier La suite de ce chapitre est en cours de rédaction, merci de patienter !

2.8 Quelques modules de la librairie standard

La librairie standard Python est constituée des modules faisant partie de la distribution de base Python. Pour les utiliser, il est nécessaire de les importer avec l’instruction import (voir plus haut). Les modules standards principaux sont les suivants :

important On ne présentera ci-après que quelques-uns de ces modules, tout en nous limitant aux fonctions les plus utiles. Pour davantage d’information, souvenez-vous qu’une fois un module importé, vous pouvez accéder à sa documentation complète (description, liste des classes, fonctions et données) avec help(module). En frappant help(module.fonction), vous obtiendrez directement l’aide sur la fonction spécifiée. Sous IPython finalement, en frappant module.<tab> vous faites apparaître toutes les fonctions relatives à un module, et respectivement avec objet.<tab> les méthodes relatives à un objet ou classe.

2.8.1 os

Le module os offre une interface d’accès aux services du système d’exploitation, et ceci de façon uniforme et portable (c-à-d. fonctionnant sur tous les OS). Il contient un sous-module os.path donnant accès au objets du système de fichiers (répertoires, fichiers, liens…).

Nous ne présentons ci-après que les méthodes les plus courantes. Sachez que os implémente en outre des fonctions de gestion de processus.

Fichiers et répertoires (ci-après, chemin représente, selon la méthode, un nom de fichier ou de répertoire, précédé ou non d’un chemin relatif ou absolu) :

import os

help(os)         # => documentation du module os
os.<tab>         # (s/IPython) => liste des fonctions
help(os.getcwd)  # => aide sur fct spécifiée

os.getcwd()      # => p.ex. '/home/bonjour'
os.curdir        # => '.'
os.listdir()     # => liste fichiers répertoire courant
os.mkdir('tes')          # création sous-répertoire
os.rename('tes', 'test') # renommage
'test' in os.listdir()   # => True
os.chdir('test') # changement répertoire courant
open('essai.txt','a').close() # comme 'touch' Unix
    # => crée fichier s'il n'existe pas et le referme
os.path.exists('essai.txt')  # => True
os.path.isfile('essai.txt')  # fichier ? => True
fich = os.path.abspath('essai.txt')
                 # => /home/bonjour/test/essai.txt
os.path.dirname(fich)  # path => /home/bonjour/test
nomf = os.path.basename(fich) # fichier => essai.txt
os.path.splitext(nomf) # => ('essai', '.txt')
os.remove('essai.txt') # destruction fichier
os.chdir('..')         # on remonte d'un niveau
os.path.isdir('test')  # répertoire ? => True
os.rmdir('test')       # destruction répertoire

for dirpath, dirnames, filenames in os.walk(os.curdir):
    for fp in filenames:
        print(os.path.join(os.path.abspath(dirpath),fp))
# => affiche récursivement tous les fichiers
#    de l'arborescence avec leur path absolu

os.environ         # => dict. avec toutes var. env.
os.environ['USER'] # => 'bonjour'
os.environ['HOME'] # => '/home/bonjour'

os.system('ls -a') # passe la commande au shell...
    # et affiche résultat sur la sortie standard

list_dir = os.popen('ls -1').read()
    # idem mais récupère résultat sur chaîne
print(list_dir)

Environnement du système d’exploitation :

Exécution de commandes shell et programmes externes :

2.8.2 glob

Le module glob a pour seul objectif de récupérer, pour le répertoire courant ou spécifié, la liste des fichiers dont le nom correspond à certains critères (pattern matching) définis au moyen des caractères suivants :

La fonction .glob(pattern) retourne une liste de noms de fichiers/répertoires, et la fonction .iglob(pattern) retourne un itérateur.

Si la pattern inclu un chemin, la recherche s’effectue dans le répertoire correspondant, et les noms de fichiers retournés inclueront ce chemin.

Soit le répertoire contenant les fichiers suivants :

    ess1.dat    ess2.dat    fi1.txt    fi2.txt
    fich1.txt   z2.txt

Quelques exemples de matching :

import glob

glob.glob('*.dat')    # => ['ess1.dat', 'ess2.dat']
glob.glob('f??.*')    # => ['fi1.txt', 'fi2.txt']
glob.glob('[ef]*2.*') # => ['fi2.txt', 'ess2.dat']
glob.glob('[!f]*.*')  # => ['ess1.dat', 'ess2.dat']

glob.glob('../r*.pdf') # => retournerait p.ex.
  # ['../r1.pdf', '../r2.pdf'] , donc avec chemin !

# Si l'on fait un glob dans un autre répertoire et
# qu'on veut récupérer les noms de fichiers sans leurs
# chemins, on peut faire une compréhension list
# utilisant os.path.basename pour enlever le chemin :
[os.path.basename(f) for f in glob.glob('path/*.txt')]

2.8.3 shutil

Le module shutil, abréviation de shell utilities, offre des fonctions intéressantes de copie et archivage de fichiers et arborescences de répertoires.

2.8.4 filecmp

Le module filecmp permet de tester si des fichiers sont identiques, par comparaison individuelle ou par contenu de répertoire :


Si l’on est intéressé à afficher les différences de contenu entre des fichiers, on se tournera vers le module difflib.

# Comparaison de 2 fichiers ____________________________

import filecmp

if filecmp.cmp('f1.dat', 'f2.dat'):
    print('Fichiers identiques')
else:
    print('Fichiers différents')


# Comparaison de 2 répertoires _________________________
#
# Soient 2 répertoires contenant les fichiers suivants
# - Répertoire :   dir1     dir2      Remarques :
# - Fichiers :     a.txt    a.txt     identiques
#                  b.txt    b.txt     différents
#                  c.txt     --       unique
#                   --      d.txt     unique

import filecmp, os

set1 = { fn for fn in os.listdir(dir1)
            if os.path.isfile(os.path.join(dir1,fn)) }
   # set contenant le nom de tous les fichiers de dir1
   # (if exclut les noms des éventuels sous-répertoires)
set2 = { fn for fn in os.listdir(dir2)
            if os.path.isfile(os.path.join(dir2,fn)) }
   # set contenant le nom de tous les fichiers de dir2

res = filecmp.cmpfiles(dir1, dir2, set1 | set2)
   # on considére les fichiers de noms correspondant
   #     à l'union des 2 sets set1 et set2 =>
   # res[0] = ['a.txt'] : liste fich. identiques
   # res[1] = ['b.txt'] : liste fich. différents
   # res[2] = ['c.txt', 'd.txt'] : fichiers uniques

2.8.5 sys, platform

Le module sys fournit une interface à l’environnement de l’interpréteur Python.

import sys

sys.platform   # => 'linux2'
sys.version
  # => '3.2.3 (Sep 25 2013, 18:22:43) \n[GCC 4.6.3]'
sys.executable # => '/usr/bin/python'
sys.path       # => path de recherche des modules
sys.getrecursionlimit()  # => 1000
sys.dont_write_bytecode  # => False


# Ex. d'utilisation .stdin .stdout .stderr
chaine = sys.stdin.readline()
sys.stdout.write(chaine)
sys.stderr.write('erreur: ...')


# Pour sortir d'un script
sys.exit('Sortie du script')

Les méthodes et attributs ci-dessous sont beaucoup plus techniques et probablement moins utiles pour vous :

 

Le module platform fournit des informations assez techniques sur la plateforme matérielle sur laquelle s’exécute le programme.

import platform

platform.uname()   # => OS, nom de machine...
platform.system()  # => 'Linux'
platform.release() # => '3.2.0-56-generic'
platform.dist()    # => ('Ubuntu', '12.04', 'precise')
platform.architecture()   # => ('64bit', 'ELF')
platform.python_version() # => '3.2.3'

2.8.6 math, random

Le module math donne accès aux fonctions de la librairie mathématique standard C. Pour le support des nombres complexes, voir les fonctions de même nom du module cmath. Les différentes catégories de fonctions sont :

Le module random fournit des générateurs aléatoires.

Citons notamment : .random(), .uniform(debut, fin), .randint(debut, fin), .randrange (debut, fin, pas), .gauss(mu, sigma), etc…

2.8.7 time

Le module time offre une interface à l’horloge système (date/heure courante, pause…) ainsi que des fonctions de conversion de format.

important Contrairement au module datetime (qui gère les dates sous forme d’objets depuis le début de notre ère), celui-ci gère en interne les dates/heures sous forme de réel flottant (dates numériques) qui expriment le temps écoulé en secondes depuis l’origine ici définie au 1.1.1970 à 00:00 UTC (GMT) (dénommée epoch sous Linux). Ce module ne permet donc pas de manipuler des dates antérieures à cette origine, mais il se prête mieux que datetime au stockage de séries chronologiques (sous forme de liste de réels).

Les dates/heures peuvent se présenter sous différentes formes :

Date/heure courante et conversions :

Autres fonctions :

import time

# Instant présent
nownum = time.time() # => date/heure courante (nb. réel)
nowstruct = time.localtime(nownum)
type(nowstruct)      # => objet de type time.struct_time

# Définition d'une date/heure, attributs
dhnum = time.mktime( (2013,11,18,0,27,35,0,0,-1) )

dhstruct = time.localtime(dhnum) # => conversion objet
dhstruct.tm_year   # => 2013
dhstruct.tm_mon    # => 11
dhstruct.tm_mday   # => 18
dhstruct.tm_hour   # => 0
dhstruct.tm_min    # => 27
dhstruct.tm_sec    # => 35
dhstruct.tm_wday   # => 0 (lundi)
dhstruct.tm_yday   # => 322 ème jour de l'année
dhstruct.tm_isdst  # => 0 (heure d'hiver)

# Formatage
time.strftime('It\'s %A %d %B %Y at %H:%M:%S', dhstruct)
  # => "It's Monday 18 November 2013 at 00:27:35"
time.strftime('%je jour de %Y, %Uème semaine', dhstruct)
  # => '322e jour de 2013, 46ème semaine'
time.ctime(dhnum)      # => 'Mon Nov 18 00:27:35 2013'
time.asctime(dhstruct) # => 'Mon Nov 18 00:27:35 2013'

# Autres
time.sleep(2.5)    # => effectue pause 2.5 sec
time.timezone      # => -3600
time.tzname        # => ('CET', 'CEST')

En guise d’illustration de ce que l’on vient de voir, construisons une série chronologique affichant ce qui suit (dates/heures toutes les 6 heures entre 2 dates) :

    20-11-2013 00:00:00
    20-11-2013 06:00:00
    20-11-2013 12:00:00
    20-11-2013 18:00:00
    21-11-2013 00:00:00
    21-11-2013 06:00:00
    21-11-2013 12:00:00
    21-11-2013 18:00:00
    22-11-2013 00:00:00
import time

# Série chronologique de 'deb' -> 'fin' avec pas 'step'
deb = time.mktime((2013,11,20,0,0,0,0,0,-1)) # date num.
fin = time.mktime((2013,11,22,0,0,0,0,0,-1)) # date num.
step = 6*60*60     # 6 heures exprimées en secondes

datenum = deb
while datenum <= fin:
    jourstruct = time.localtime(datenum)
    print(time.strftime('%d-%m-%Y %H:%M:%S',jourstruct))
    datenum += step

Formatage de dates et heures avec strftime

La fonction .strftime du module time et la méthode du même nom du module datetime admettent notamment les codes de formatage suivants :

2.8.8 datetime

Le module datetime est totalement orienté-objet (contrairement au module time). Il implémente un certain nombre de classes (avec attributs et méthodes associés) permettant de manipuler : dates avec heures (classe .datetime), dates seules (classe .date), heures seules (classe .time), différences de temps (classe .timedelta). Les dates se réfèrent au calendrier Grégorien et peuvent être manipulées dans la plage d’années 0001 à 9999 de notre ère.

A) La classe .datetime et ses objets dateheure :

On peut finalement comparer des dates/heures en comparant des objets .datetime ou .date avec les opérateurs < et >

import datetime as dt  # nom abrégé 'dt' pour simplifier

# Instant présent
now = dt.datetime.today() # => (2013, 11, 19, 17, 32, 36)
type(now)  # => objet de type datetime.datetime
print(now) # => affiche: 2013-11-19 17:32:36.780077

# Définition d'une date/heure, attributs
dh = dt.datetime(2013,11,18,0,27,35)
dh.year            # => 2013
dh.month           # => 11
dh.day             # => 18
dh.hour            # => 0
dh.minute          # => 27
dh.second          # => 35

# Formatage
dh.strftime('It\'s %A %d %B %Y at %H:%M:%S')
  # => "It's Monday 18 November 2013 at 00:27:35"
dh.strftime('%jème jour de %Y, dans la %Uème semaine')
  # => '322ème jour de 2013, dans la 46ème semaine'
dh.ctime()      # => 'Mon Nov 18 00:27:35 2013'
dh.isoformat()  # => '2013-11-18T00:27:35'

# Manipulations
now > dh           # => True
delta = now - dh   # => (1, 61501) : jours et secs
type(delta)        # => datetime.timedelta
dh - now           # => (-2, 24899)
    # i.e. reculer de 2 jours et avancer de 24899 sec.

# Conversions de types
dh.date()  # => objet date (2013, 11, 18)
dh.time()  # => objet time (0, 27, 35)

B) La classe .date n’est qu’une simplification de la classe .datetime en ce sens qu’elle ne contient que la date (sans heure) :

Pour le reste, les méthodes de l’objet .datetime vues plus haut s’appliquent également.

import datetime as dt  # nom abrégé 'dt' pour simplifier

# Jour courant
auj = dt.date.today() # => objet (2013, 11, 19)
type(auj)          # => objet de type datetime.date
print(auj)         # => affiche: 2013-11-19

# Attributs
auj.year           # => 2013
auj.month          # => 11
auj.day            # => 19

# Manipulations
proch_anni = dt.date(auj.year, 2, 8)
if proch_anni < auj:
    proch_anni = proch_anni.replace(year=auj.year+1)
nb_jours = proch_anni - auj
print(proch_anni.strftime('Next birthday: %A %d %B %Y'))
print('In: %d days' % (nb_jours.days) )  # => affiche :
  # Next birthday: Saturday 08 February 2014
  # In: 81 days

C) La classe .time est également une simplification de la classe .datetime en ce sens qu’elle ne contient que l’heure (sans date) et que ses méthodes sont très limitées :

import datetime as dt  # nom abrégé 'dt' pour simplifier

# Définition d'heures
heure = dt.time(9,45,10) # => objet (9, 45, 10)
type(heure)        # => objet de type datetime.time
print(heure)       # => affiche: 09:45:10

# Attributs
heure.hour         # => 9
heure.minute       # => 45
heure.second       # => 10
heure.microsecond  # => 0

D) La classe .timedelta permet d’exprimer une différence de temps entre des objets .datetime ou .date

import datetime as dt  # nom abrégé 'dt' pour simplifier

# Définition de dates/heures
dh0 = dt.datetime(2013,11,20)  # le 20.11.2013 à 0:00:00
dh1 = dt.datetime(2013,11,21,2,15,30,444)
               # le 21.11.2013 à 2:15:30 et 444 microsec

# Calcul de timedelta, attributs
delta = dh1 - dh0  # => (1, 8130, 444) : jour,sec,micro
  # mais:  dh0 -dh1  => (-2, 78269, 999556) , donc
  #        reculer de 2 jours et avancer de 78269 sec...
type(delta)    # => objet de type datetime.timedelta
print(delta)   # => affiche: 1 day, 2:15:30.000444

# Attributs et méthodes
delta.days             # => 1
delta.seconds          # => 8130
delta.microseconds     # => 444
delta.total_seconds()  # 94530.000444  (secondes)

Nous reprenons ci-contre l’idée de la série chronologique du chapitre précédent (dates/heures toutes les 6 heures entre 2 dates), en l’implémentant ici avec les objets .datetime et .timedelta. Elle affiche la série ci-dessous :

    20-11-2013 00:00:00
    20-11-2013 06:00:00
    20-11-2013 12:00:00
    20-11-2013 18:00:00
    21-11-2013 00:00:00
    21-11-2013 06:00:00
    21-11-2013 12:00:00
    21-11-2013 18:00:00
    22-11-2013 00:00:00
import datetime as dt  # nom abrégé 'dt' pour simplifier

# Série chronologique
deb = dt.datetime(2013,11,20)  # objet datetime
fin = dt.datetime(2013,11,22)  # objet datetime
step = dt.timedelta(hours=6)   # objet timedelta

dateheure = deb
while dateheure <= fin:
    print(dateheure.strftime('%d-%m-%Y %H:%M:%S'))
    dateheure += step

2.8.9 calendar

Le module orienté-objet calendar permet de manipuler des calendriers, un peu à la façon de la commande cal Unix.

Quelques fonctions :

Ci-dessous, 1erjour définit le numéro de jour de la semaine auquel débute l’affichage des semaines. Si l’on omet ce paramètre, c’est par défaut 0=lundi.

Les classes .TextCalendar et .LocaleTextCalendar permettent de créer des calendriers mensuels/annuels sous forme texte :

Les classes .HTMLCalendar et .LocaleHTMLCalendar permettent de créer des calendriers mensuels/annuels HTML :

Finalement, la classe .Calendar permet de créer des listes de dates ou créer des itérateurs. Nous vous renvoyons à la documentation pour les détails.

import calendar as cal

# Fonctions
cal.weekday(2013,11,19)     # => 1 (lundi)
cal.monthcalendar(2013,11)  # => liste/matrice :
      #   [[ 0,  0,  0,  0,  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,  0]]
cal.monthrange(2013,11)     # => (4, 30)
cal.isleap(2012)            # => True


# Classes TextCalendar et LocaleTextCalendar
catext = cal.LocaleTextCalendar(locale='fr_FR.UTF-8')
catext.prmonth(2013,11)     # => affiche :
      #      novembre 2013
      #   lu ma me je ve sa di
      #                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
catext.pryear(2013)  # => voir lien 1. ci-dessous


# Classes HTMLCalendar et LocaleHTMLCalendar
# (on écrit ci-dessous directement dans fich. HTML)
cahtml=cal.LocaleHTMLCalendar(locale='fr_FR.UTF-8')

with open('cal-nov-2013.html', 'w') as fd:
    fd.write(cahtml.formatmonth(2013,11))
    # => voir lien 2. ci-dessous

with open('cal-2013.html', 'w') as fd:
    fd.write(cahtml.formatyear(2013))
    # => voir lien 3. ci-dessous

Illustrations des exemples ci-dessus (il resterait bien entendu à styler les deux sorties HTML avec du CSS) :

  1. LocaleTextCalendar.pryear
  2. LocaleHTMLCalendar.formatmonth
  3. LocaleHTMLCalendar.formatyear

2.8.10 this

Pour conclure sur une touche humoristico-philosophique cette présentation des modules les plus importants de la librairie standard, voici le module this qui n’a pas d’autre but que de résumer la philosophie Python. Il suffit de l’importer… et vous verrez alors apparaître le texte ci-dessous d’un gourou Python !

The Zen of Python, by Tim Peters :

import this

# <= cela affiche le texte ci-contre !

Programmation objet avec Python

Voir le support de cours de Samuel Bancal.

Scientific Python

Voir les supports de cours de Samuel Bancal relatifs à l’usage des librairies suivantes :

Installation et utilisation de Python et outils associés

Voir cette page.

 


cc-by-sa Jean-Daniel Bonjour, 2013-2021