Vous êtes sur la page 1sur 114

PYTHON

JACK FELLERS
TABLE DES MATIÈRES

Sans titre

1. Programmation
2. Variables et expressions
3. Exécution conditionnelle
4. Fonctions
5. Itérations
6. Listes
7. Dictionnaires
8. Ensembles
9. Programmes Python
10. Extension des programmes
11. Script Python

Conclusion
SANS TITRE

Python
1

PROGRAMMATION

A uencours de ce e-book, nous allons tenter de vous transformer en expert


programmation. Au final, vous serez un programmeur, certainement
pas un programmeur professionnel, mais au moins vous aurez les
compétences nécessaires afin d’examiner un problème et de développer un
programme pour le résoudre. D’une certaine façon, il faut deux
compétences pour être un programmeur:
• Premièrement, vous devez connaître le langage de programmation
(Python dans le cas présent), qui comprend du vocabulaire et de la
grammaire. Vous devez être aussi capable d’orthographier correctement les
mots dans cette nouvelle « langue » ainsi que de construire des « phrases »
bien formées dans cette nouvelle « langue » ;
• Deuxièmement, vous devez combiner des mots et des phrases afin de
créer une histoire et transmettre une idée. En effet, en programmation, notre
programme est l’ « histoire » et le problème que vous tentez de résoudre est
l’« idée ».
Après avoir appris un langage de programmation comme Python, il sera
beaucoup plus simple d’apprendre un deuxième langage de programmation
comme JavaScript ou C++. L’ensemble des langages de programmation a
un vocabulaire et une grammaire différents, mais les compétences sur le
plan de résolution de problèmes seront les mêmes dans l’ensemble des
langages de programmation. Vous apprendrez le « vocabulaire » et les
« phrases » de Python assez rapidement, car ils sont très faciles. L’écriture
d’un programme cohérent afin de résoudre un problème peut prendre plus
de temps, car vous devez en premier lieu apprendre les bases. Tout comme
nous apprenons la langue italienne, nous apprenons d’abord à écrire des
mots, ensuite nous serons en mesure de lire et d’expliquer des programmes.
Quand vous commencerez à programmer, vous réalisez que la
programmation deviendra un processus vraiment agréable et créatif.
Commençons par le vocabulaire et la structure des programmes Python,
soyez patient parce que nous utiliserons des exemples simples, mais très
utiles.

Mots clés
Contrairement aux langues humaines, le vocabulaire de Python est en fait
assez restreint. Ce « vocabulaire » constitue les « mots réservés », cela
signifie que les mots qui ont une signification très particulière pour Python.
Quand vous écrivez vos programmes, vous pouvez créer vos propres mots,
cela veut dire que des mots qui ont une signification essentielle pour vous et
qui sont nommés variables.
Vous pouvez choisir des noms pour vos variables, mais vous ne pouvez
pas utiliser l’un des mots réservés de Python comme nom de variable. Les
mots réservés en Python sont comme illustrés ci-dessous :
and del global not with as elif if or yield assert else import pass break
except in raise class finally is return continue for lambda try def from
nonlocal while
Nous apprendrons peu à peu ces mots réservés et la manière dont ils
sont utilisés au cours de ce e-book. Nous pouvons forcer Python à afficher
un message à l’aide de l’instruction suivante:
print ('Hello World!')
De cette manière, nous avons écrit notre première phrase Python
syntaxiquement correcte.
Notre phrase débute par la fonction print suivie d’une chaîne de texte de
notre choix entre guillemets. Les chaînes de caractères dans les instructions
d’impression sont entourées de guillemets simples ou doubles. La majorité
des programmeurs utilisent des guillemets simples, mais dans quelques cas,
cela peut poser un problème parce qu’ils peuvent être mal interprétés par
Python.
Maintenant que nous avons un mot et une phrase facile que nous
connaissons en Python, nous devons savoir comment entamer une
conversation avec Python afin d’interagir avec lui.
Avant de pouvoir converser avec Python, vous devez installer le
software sur votre computer et découvrir la façon de lancer Python sur votre
computer. Le processus d’installation est vraiment simple et guidé par une
interface graphique soignée. Vous trouverez de plus amples informations
sur le site officiel suivant https://www.python.org/downloads/. Voici une
excellente nouvelle : si vous êtes un utilisateur de Linux ou de Mac OS,
Python est vraiment probablement déjà installé sur votre système. Dans tous
les cas, ouvrez une fenêtre de terminal et tapez la commande python3 et, si
elle est installée, l’interpréteur Python commencera à fonctionner en mode
interactif et quelque chose de similaire apparaîtra. Observez l’exemple ci-
dessous :
pi@raspberrypi:~ $ python3
Python 3.7.3 (default, Dec 20 2019, 18:57:59)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
Prompt >>> est la manière dont l’interpréteur Python vous demande la
question suivante : « Que voulez-vous faire maintenant ? ». Python est prêt
à converser avec vous. Tout ce que vous devez faire, c’est parler le langage
Python.
Vous pouvez imprimer quelques phrases sur l'écran, comme dans
l’exemple suivant :
print('Bonjour')
print('Je viens en paix')
print('Je voudrais apprendre Python')
print 'parce qu'il est très puissant'
File "<stdin>", line 1
print 'parce qu'il est très puissant
^
SyntaxError: Missing parentheses in call to 'print'
>>>

La conversation s’est bien déroulée pendant un moment, puis vous avez fait
une petite erreur en utilisant le langage Python. À ce stade, vous réalisez
que, même si Python est incroyablement difficile et puissant, il est vraiment
pointilleux sur la syntaxe que vous utilisez. En fait, Python n’est pas aussi
intelligent que vous le pensez, vous ne faites que converser avec vous-
même, mais vous utilisez la syntaxe requise. En un sens, quand vous
utilisez un programme écrit par quelqu’un d’autre, la conversation se
déroule entre vous et les autres programmeurs qui utilisent Python comme
intermédiaire.
Python est utilisé afin d’exprimer comment la conversation doit se
dérouler.
Pour mettre fin à une conversation avec Python, vous pouvez fermer la
fenêtre du terminal ou, de façon plus élégante, utiliser la commande quit():
>>> quit()

Interprète et compilateur
Python est un langage de haut niveau conçu afin d’être simple à lire et à
écrire pour les humains et simple à lire et à traiter pour les computer. Les
autres langages de haut niveau sont Java, C++, PHP, Ruby, Basic, Perl,
JavaScript et bien d’autres. Le hardware à l’intérieur de la Central
Processing Unit (CPU) ne comprend aucun de ces langages de haut niveau ;
en fait, CPU comprend un langage nommé langage machine. Le langage
machine est vraiment simple et franchement vraiment ennuyeux à écrire
parce qu’il n’est représenté que par des zéros et des uns qui rappellent
tellement la Matrice :
001010001110100100101010000001111
11100110000011101010010101101101

Le langage machine semble assez facile en apparence, puisqu’il n’y a que


des zéros et des uns, mais sa syntaxe est beaucoup plus complexe que celle
du Python, si bien que très peu de programmeurs écrivent en langage
machine. Nous avons tendance à construire plusieurs traducteurs afin de
permettre aux programmeurs d’écrire dans des langages de haut niveau
comme Python ou JavaScript, et ces traducteurs se chargent de convertir les
programmes en langage machine pour leur exécution effective par la CPU.
Les programmes écrits dans des langages de haut niveau peuvent être
déplacés entre divers computer en utilisant un interpréteur différent sur la
nouvelle machine ou en recompilant le code afin de créer une version du
langage du programme pour la nouvelle machine. Ces traducteurs de
langage de programmation se répartissent en deux catégories générales
nommées les interprètes et les compilateurs.
Un interprète lit le code source du programme comme il a été écrit par
le programmeur, analyse le code source et interprète les instructions à la
volée. Python est un interpréteur et quand nous exécutons Python en mode
interactif, nous sommes en mesure de taper une commande (ou une phrase)
Python valide et il la traite tout de suite et redevient disponible afin
d’ajouter une autre requête.
Quelques lignes demandent à Python de se souvenir d’une valeur pour
le futur, nous devons alors choisir un nom afin de stocker cette valeur.
Habituellement, le terme variable est utilisé afin de désigner les étiquettes
de ces valeurs:
>>> x = 5
>>> print(x)
5

>>> y = x * 4
>>> print(y)
20

Dans l’exemple ci-dessus, nous demandons à Python de se souvenir de la


valeur cinq et d’utiliser l’étiquette x pour pouvoir récupérer cette valeur
ultérieurement. Nous vérifions que Python a bien mémorisé la valeur en
utilisant print. Nous demandons par la suite à Python de récupérer x, de le
multiplier par quatre et de placer la valeur que nous venons de calculer dans
y. Finalement, nous demandons à Python d’imprimer la valeur actuellement
dans y.
Même si nous tapons ces commandes dans Python une par une, Python
les traite comme une séquence ordonnée d’instructions où les instructions
suivantes sont en mesure de récupérer les données créées par les
instructions précédentes. Nous écrivons notre premier "paragraphe" avec
quatre phrases dans un ordre logique et significatif.
Un compilateur, quant à lui, doit stocker l’ensemble du programme dans
un file, et ensuite effectuer un processus de traduction du code source de
haut niveau en langage machine, et enfin le compilateur place le langage
machine résultant dans un file pour exécution. Si vous possédez un système
Windows, ces programmes exécutables en langage machine ont
fréquemment un suffixe ".exe" ou ".dll" qui veut dire respectivement
"exécutable" et "linklibrary dynamique". Dans Linux et Macintosh, il n’y a
pas de suffixe qui marque de façon unique un file comme exécutable.
L’interpréteur Python est écrit dans un langage de haut niveau nommé
C. Vous pouvez consulter le code source de l’interpréteur Python en visitant
le site officiel à cette adresse suivante https://www.python.org/ comme il
s’agit d’un langage open source, tout le monde peut contribuer à son
développement. Quand vous avez installé Python sur votre computer (ou
que votre fournisseur l’a installé), vous avez
fait une copie du code machine du programme Python traduit pour votre
système.

Un programme
La définition d’un programme, dans sa forme la plus facile, est une
séquence d’instructions Python qui a été créée afin de résoudre un
problème. Même une seule instruction print peut être vue comme un
programme. C’est un programme d’une ligne et pas nécessairement utile,
mais dans la définition la plus stricte, c’est un programme Python. Il peut
être plus simple de comprendre ce qu’est un programme en pensant au
problème pour lequel le programme a été conçu, puis en considérant que le
programme aide à résoudre ce problème.
Supposons que vous fassiez des recherches sur les post de Facebook et
que vous soyez intéressé par le mot le plus souvent utilisé dans une série de
post. Vous pourriez imprimer le flux de post de Facebook et scanner le texte
à la recherche du mot le plus fréquent, mais cela prendrait beaucoup de
temps et serait source d’erreurs. Vous pourriez écrire un programme Python
afin d’effectuer cette tâche avec rapidité et précision, mais surtout, afin de
pouvoir terminer cette analyse rapidement.
Imaginez devoir effectuer cette tâche en analysant des millions de lignes
de texte. Honnêtement, il serait plus simple et plus efficace pour vous
d’apprendre Python et d’écrire un programme afin de compter les mots que
de scanner l’ensemble des mots manuellement. Vous n’avez pas besoin de
centaines de lignes pour un programme aussi simple, vous en avez besoin
de moins de 20. Ne pensez-vous pas que c’est possible ? C’est ici dans
l’exemple suivant :
nomFile = input('Insérer le file:')
file = open(nomFile, 'r')
compteur = dict()

for ligne in file :


mots = ligne.split()
for mot in mots:
compteur[mot] =
compteur.get(mot, 0) + 1

bigcompt = None
bigmot = None
for mot, compt in
list(compteur.items()) :
if bigcompt is None or compt > bigcompt:
bigmot = mot
bigcompt = compt

print(bigmot, bigcompt)

En lisant ce code, vous observez un mélange d’italien et de français, ce qui


est normal dans la phase d’apprentissage, car pour rendre le code plus
lisible, nous recherchons des termes de notre propre vocabulaire au lieu de
l’anglais. Toutefois, il est important d’apprendre à écrire le code
entièrement en anglais pour qu’il puisse être compris par tout le monde sans
avoir recours à un traducteur, surtout si nous travaillons sur des projets open
source.
Finalement, il est important de noter qu’il existe des constructions
conceptuelles de bas niveau que nous utilisons afin de construire des
programmes. Ces constructions ne sont pas uniquement destinées aux
programmes Python, mais font partie de l’ensemble des langages de
programmation, du langage machine aux langages de haut niveau.

Exécution conditionnelle: vérification de certaines conditions, puis


exécution ou saut d’une séquence d’instructions;
Exécution répétée: exécution répétée de certains ensembles
d’instructions, en général avec une certaine variation;
Exécution séquentielle: exécution des instructions les unes après
les autres dans l’ordre où elles se situent dans le script;
Input: obtention de données du "monde extérieur". Il peut s’agir de
données de lecture ou même d’un capteur comme un microphone
ou un GPS. Notez que dans notre cas, l’input provient de
l’utilisateur qui tape des données sur le clavier;
Output: afficher les résultats du programme sur un écran ou les
sauvegarder dans un file, voire les écrire sur un appareil comme un
CD afin de jouer de la musique;
Réutilisation: écrivez une série d’instructions une fois et identifiez-
les de façon à ce qu’elles puissent être réutilisées dans tout le
programme.

Cela semble presque trop facile pour être vrai, et bien sûr, ce n’est
jamais aussi facile. L’art d’écrire un programme consiste à composer et à
entrelacer ces éléments de base, encore et encore, afin de produire quelque
chose d’utile pour vos utilisateurs. Le programme de comptage des mots de
l’exemple ci-dessus utilise directement tous ces modèles sauf un, et à la fin
du livre, vous serez en mesure de trouver lequel.
2

VARIABLES ET EXPRESSIONS

U ne valeur est l’un des éléments de base avec lesquels un programme est
en mesure de travailler, par exemple une lettre ou un chiffre. Jusqu’à
maintenant, nous n’avons vu que des nombres et des chaînes de caractères
(séquences de caractères) comme valeurs, celles-ci appartenant à des types
différents : les nombres entiers et les chaînes de caractères. Vous (et
l’interpréteur) pouvez identifier les chaînes de caractères parce qu’elles sont
entourées de guillemets (simples ou doubles) et peuvent être imprimées
avec l’instruction print, qui fonctionne aussi pour les nombres entiers. Nous
utilisons la commande python3 afin de démarrer l’interpréteur.
python3
>>> print(4)
4

Si vous voulez savoir quel type a une valeur, l’interprète peut vous aider
comme suit :
>>> type('Hello World!')
<class 'str'>
>>> type(19)
<class 'int'>

Sans aucune surprise, les chaînes de caractères appartiennent au type str et


les entiers au type int. De façon moins évidente, les nombres avec une
virgule appartiennent à un type nommé float, car ces nombres sont
représentés dans un format nommé en fait floating point:
>>> type(0.2)
<class 'float'>

Notez que toute valeur placée entre guillemets est en fait évaluée comme
une chaîne de caractères comme illustrée ci-dessous :
>>> type('13')
<class 'str'>
>>> type('1.2')
<class 'str'>

De même, quand vous tapez un vraiment grand nombre entier, vous pouvez
être tenté par l’utilisation de virgules séparant les groupes de trois chiffres,
comme dans 1,000, 000. Ce n’est pas un entier valide en Python, mais il ne
retournera pas d'erreur :
>>> print(1,000,000)
100

Ce n’est pas du tout ce que nous attendions, parce que Python interprète
1,000,000 comme une séquence d’entiers séparés par une virgule.
Honnêtement, il aurait été mieux de renvoyer une erreur, mais c’est le
premier exemple d'une erreur sémantique, cela signifie que le code
s’exécute sans produire de message d’erreur, mais ne fait pas ce que nous
souhaitons.

Variables
L’une des caractéristiques les plus fondamentales d’un langage de
programmation est la possibilité de manipuler des variables. Il faut noter
qu’une variable est un nom qui fait référence à une valeur. Une instruction
d’affectation crée de nouvelles variables et leur fournit des valeurs :
>>> message = 'Welcome to Python'.
>>> number = 1522
>>> piGreek = 3.1415926535897931

Dans cet exemple ci-dessus, trois affectations sont créées. La première


instruction affecte une chaîne de caractères à une nouvelle variable portant
le nom message. La deuxième affecte l’entier 1522 à nombre. La troisième
affecte la valeur (approximative) de π à piGreco. Afin d’afficher la valeur
d’une variable, nous pouvons utiliser l’instruction print :
>>> print(number)
1522
>>> print(pi)
3.141592653589793

Les programmeurs choisissent en général des noms significatifs pour les


variables et qui documentent ce à quoi la variable est utilisée. Les noms des
variables peuvent être arbitrairement longs et peuvent contenir des lettres et
des chiffres, mais ne peuvent pas débuter par un chiffre. Il est permis
d’utiliser des lettres majuscules. Cependant, il est bon de commencer les
noms de variables par une lettre minuscule.
Le caractère de soulignement (_) peut apparaître dans le nom d’une
variable. Effectivement, il est fréquemment utilisé dans les noms à plusieurs
mots, comme pi_greek ou nom_utilisateur. Les noms des variables peuvent
débuter par un caractère de soulignement, mais nous évitons en général de
le faire, sauf si nous écrivons du code de bibliothèque que d’autres
utilisateurs peuvent utiliser.
L’attribution d’un nom non valide à une variable entraîne une erreur de
syntaxe :
>>> 2orchestra = 'Grand Tail'.
SyntaxError: invalid syntax

Opérateurs
Les opérateurs sont des symboles spéciaux qui représentent des calculs
arithmétiques comme l’addition et la soustraction. Les valeurs auxquelles
l’opérateur est appliqué sont nommées opérandes.
Les opérateurs +, -, *, / et ** effectuent respectivement l’addition, la
soustraction, la multiplication, la division et l’exposant, comme dans les
exemples illustrés ci-dessous :
20 + 32
hour - 1
hour * 60 + minutes
minutes / 60
5 ** 2
(5+9) * (15-7)

Une expression est une combinaison de valeurs, de variables et


d’opérateurs. Il est important de noter qu’une valeur, par elle-même, est
considérée comme une expression, tout comme une variable. Les
expressions ci-dessous sont ainsi toutes légales (en supposant qu’une valeur
a été attribuée à la variable x) :
12
x
x + 11

Quand plus d’un opérateur apparaît dans une expression, l’ordre


d’évaluation dépend des règles de précédence. De plus, pour les opérateurs
mathématiques, Python suit les conventions mathématiques.
L’acronyme PEMDAS est un moyen utile de se souvenir des règles
suivantes :

Les parenthèses ont la plus haute préséance et peuvent être


utilisées afin de forcer une expression à être évaluée dans l’ordre
voulu. Comme les expressions entre parenthèses sont évaluées en
premier, 2*(3-1) donnera 4 et (1+1)**(5-2) donnera 8. Vous
pouvez aussi utiliser des parenthèses afin de faciliter la lecture
d’une expression comme dans (minute*100) / 60, même si cela ne
change pas le résultat ;
L’exposant a la plus haute priorité après les parenthèses. Donc,
2**1+1 renverra 3 et non 4, tout comme 3*1**3 renverra 3 et non
27 ;
La multiplication et la division ont la même préséance, qui est
supérieure à celle de l’addition et de la soustraction, qui ont
également la même préséance. Donc, 2*3-1 donnera 5 et non 4,
alors que 6+4/2 donnera 8 et non 5 ;
Les opérateurs ayant la même précédence sont évalués de gauche à
droite. Ainsi, l’expression 5-3-1 renverra 1 et non 3, car 5-3
apparaît en premier et donc 1 est soustrait de 2.

Il est essentiel de noter que l’opérateur modulo, quant à lui, fonctionne sur
des entiers et renvoie le reste du premier opérande divisé par le second. En
Python, l’opérateur modulo est caractérisé par le signe de pourcentage (%).
La syntaxe est la même que pour les autres opérateurs. Observez l’exemple
ci-dessous :
>>> quotient = 7 / 3
>>> print(quotient)
2,33333333
>>> rest = 7 % 3
>>> print(rest)
1

Donc, 7 divisé par 3 donne 2 avec un reste de 1. L’opérateur modulo est


étonnamment utile dans la mesure où vous pouvez vérifier si un nombre est
divisible par un autre étant donné que si x%y est égal à zéro, alors x est
divisible par y. Cela vous permettrait ainsi de résoudre des problèmes
mathématiques de façon beaucoup plus rapide comme dans cet exemple
suivant : pourriez-vous me dire combien de bonbons il me resterait si je
voulais distribuer 12059 bonbons à 2164 enfants ? La réponse est
12059%2164 = 1239 bonbons restants.
L’opérateur + est aussi utilisé afin de travailler avec des chaînes de
caractères, mais n’indique pas d’addition au sens mathématique du terme.
Cet opérateur, utilisé avec des chaînes de caractères, effectue une
concaténation, cela signifie qu’il joint des chaînes de caractères en les
reliant entre elles. Observez l’exemple ci-dessous :
>>> number1 = 10
>>> number2 = 15
>>> print(number1+number2)
25

>>> number1 = '100'


>>> number2 = '150'
>>> print(number1 + number2)
100150

De même, l’opérateur * fonctionne aussi avec les chaînes de caractères,


mais en multipliant le contenu d’une chaîne par un nombre entier. Voici un
autre exemple illustré ci-dessous :
>>> string = 'Try '
>>> numberOfRepetitions = 3
>>> print(string * numberOfRepetitions)
Try Try Try

Récupération de l’input de l'utilisateur


Quelquefois, nous aimerions récupérer la valeur d’une variable auprès de
l’utilisateur via son clavier. Python donne une fonction intégrée nommée
input qui fait exactement cela. Quand cette fonction est invoquée, le
programme s’arrête et attend que l’utilisateur tape quelque chose. Quand
l’utilisateur appuie sur la touche Enter, le programme reprend et renvoie ce
que l’utilisateur a tapé sous forme de chaîne.
>>> user_variable = input()
J'aime Python
>>> print(user_variable)
J'aime Python
Avant de recevoir un input de l’utilisateur, il est une bonne idée
d’imprimer une question ou une indication pour l’utilisateur. Effectivement,
vous pouvez passer une chaîne à afficher à l’utilisateur avant de mettre en
pause l’input :
>>> nom = input('Comment tu t’appelles?\n')
Comment tu t’appelles?
Antonio
>>> print(nom)
Antonio

Le caractère spécial \n à la fin de le prompt représente une nouvelle ligne


ou provoque une interruption de la ligne en cours. Voilà pourquoi l’input de
l’utilisateur apparaît sous le prompt. Il est important de savoir que si vous
vous attendez à ce que l’utilisateur tape un nombre entier, vous pouvez en
fait tenter de convertir la valeur renvoyée en int à l’aide de la fonction int():
>>> question = 'Quel âge as-tu?\n'
>>> années = input(question)
Quel âge as-tu?
22
>>> int(années)
22

Attention, si l’utilisateur tape autre chose qu’un nombre, vous aurez une
erreur parce que la fonction int() ne se traduit pas comme prévu. Nous
observerons la manière de gérer les exceptions au Chapitre 3. Plus les
programmes sont volumineux et difficiles, plus ils sont compliqués à lire.
Les langages formels sont denses et il est souvent difficile d’analyser un
morceau de code et de saisir ce qui est fait ou pourquoi.
Voilà pourquoi il est une bonne idée d’ajouter des commentaires aux
programmes afin d’expliquer en langage naturel ce que fait le programme
ou d’expliquer pourquoi. Les commentaires en Python commencent par le
symbole #:
# Je cherche le pourcentage de ressources utilisées
percentage_disk = (use_disk * 100) / 60

Dans cet exemple ci-dessus, le commentaire a été placé sur une nouvelle
ligne, mais peut aussi être placé sur la même ligne que l’instruction.
L’interpréteur Python ignorera tout ce qui se trouve après la marque de
hachage jusqu’à la fin de la ligne. Les commentaires sont très utiles pour
documenter les caractéristiques non évidentes du code. Il est raisonnable de
penser que le lecteur peut comprendre ce que fait le code ; il est beaucoup
plus utile d’expliquer pourquoi. Il est inutile d’expliquer des affectations
faciles ; il est plus judicieux d’expliquer une expression difficile (par
exemple, une formule mathématique) en langage naturel.
Choisissez des noms de variables appropriés parce qu’ils peuvent
réduire le besoin de commentaires. Toutefois, les noms de variables longs
peuvent en fait rendre les expressions complexes difficiles à lire, il est alors
crucial de trouver un compromis.
3

EXÉCUTION CONDITIONNELLE

U ne expression booléenne est une expression vraie ou fausse. Les


exemples démontrés ci-dessous utilisent l’opérateur ==, qui compare
deux opérandes et produit True s’ils sont égaux et False sinon :
>>> 3 == 3
True
>>> 5 == 2
False

En fait, True et False sont des valeurs spéciales qui appartiennent à la classe
bool ; en fait, il faut noter que ce ne sont pas des chaînes de caractères :

>>> type(True)
<class 'bool'>
>>> type(False)
<class 'bool'>

Il existe divers types d’opérateurs afin d’effectuer des comparaisons :


x != y # x n'est pas égal à y
x > y # x est plus grand que y
x < y # x est plus petit que y
x >= y # x est supérieur ou égal à y
x <= y # x est inférieur ou égal à y

Même si ces opérations vous sont probablement familières, les symboles


Python sont différents des symboles mathématiques pour les mêmes
opérations. En fait, une erreur courante consiste à utiliser un simple signe
égal (=) plutôt qu’un double signe égal (==). Souvenez-vous que = est un
opérateur d’affectation alors que == est un opérateur de comparaison. Les
termes =< ou => n’existent pas.
Il est important de noter qu’il existe trois opérateurs logiques : and, or,
et not. La sémantique (signification) de ces opérateurs est similaire à leur
signification en anglais. L’opérateur and renvoie la valeur vraie si les deux
expressions (à sa droite et à sa gauche) sont vraies, fausse sinon ;
l’opérateur or renvoie la valeur vraie si au moins l’une d’entre elles est
vraie, fausse sinon ; l’opérateur not inverse simplement la valeur, de sorte
que fausse deviendra vraie et vice versa. Observez l’exemple démontré ci-
dessous:
>>> 1 == 1 and 2 == 2
True
>>> 1 == 1 or 2 == 3
True
>>> 1 == 2 or 2 == 3
False
>>> not(1 == 1)
False

if
Afin d’écrire des programmes utiles, nous avons presque toujours besoin de
la capacité de vérifier les conditions et de changer le comportement du
programme en conséquence. Les instructions conditionnelles nous offrent
cette possibilité et la forme la plus simple est l’utilisation de l’instruction if
:
if x > 0 :
print('J'utilise x parce qu'il est
positif')

L’expression booléenne qui suit l’instruction if se nomme une condition.


Nous terminons l’instruction if par deux points (:) alors que les lignes qui
suivent l’instruction if sont indentées. Si la condition logique est vraie,
l’instruction en retrait est exécutée, si la condition logique est fausse,
l’instruction en retrait est ignorée.
L’instruction se compose d’une ligne d’en-tête se terminant par deux
points (:), suivie d’un bloc indenté. Les déclarations de ce type sont
nommées instructions composées parce qu’elles s’étendent sur plus d’une
ligne et il n’y a pas de limite au nombre d’instructions pouvant apparaître
dans le corps, l’essentiel étant qu’il y en ait au moins une. Il est quelquefois
utile d’avoir un corps sans instructions (cela est en général utilisé lors du
développement afin d’indiquer que vous devez compléter cette section, bien
qu’il soit préférable d’utiliser un commentaire). Dans ce cas, vous êtes en
mesure d’utiliser l’instruction pass, qui n’effectue aucune action, mais agit
comme un rappel.

if x > 0 :
print('J'utilise x pour mes
calculs')

if x < 0 :
# Les valeurs négatives doivent être
traitées
pass

Quand vous utilisez l’interpréteur Python, une ligne blanche doit être
laissée à la fin d’un bloc, sinon Python renverra une erreur de syntaxe
similaire à la suivante:
>>> if 10 > 0:
... print('a')
File "<stdin>", line 2
print('a')
^
IndentationError : expected an indented block

Comme vous l’avez probablement constaté, après avoir tapé l’instruction


conditionnelle, le prompt de Python est passée de >>> à ... indiquant qu’il
s’attend à une instruction indentée, car nous sommes en fait dans le corps
d’une instruction conditionnelle.

if...else
Une deuxième forme de l’instruction if est l’exécution alternative, dans
laquelle il existe deux possibilités et la condition détermine l’action à
exécuter. La syntaxe est similaire à la suivante :
if x > 0 :
print('x est supérieur à 0')
else :
print('x est inférieur ou égal à 0')

Dans l’exemple ci-dessus, nous avons repris l’exemple précédent en


appliquant une condition qui, si elle est vérifiée, imprime un message
informant que x est supérieur à 0, sinon elle informe que x est inférieur ou
égal à 0.
Il est important de noter que comme la condition doit être vraie ou
fausse, l’une des instructions sera nécessairement exécutée. Les alternatives
sont appelées branches ( ou branch), parce qu’elles sont des branches du
flux d’exécution.

if...elif...else
Quelquefois, il y a plus de deux possibilités et nous avons besoin de plus de
deux branches. Supposons que nous voulions spécialiser davantage notre
exemple : nous souhaitons savoir quand x est égal à 0 parce qu’il ne suffit
pas de savoir qu’il est inférieur ou égal à 0.

if x < 0:
print('x est inférieur à 0')
elif x > 0:
print('x est supérieur à 0')
else:
print('x est égal à 0')

elif est l'abréviation de "else if", fréquemment utilisée dans plusieurs autres
langues. Là encore, une seule branche sera exécutée et il est important de se
souvenir qu’il n’y a pas de limite au nombre d’instructions elif. S’il y a une
clause else, elle doit être placée à la fin, mais elle ne doit pas
nécessairement s’y trouver.
Chacune des conditions est vérifiée dans l’ordre décrit. Si le premier est
faux, le suivant est vérifié et ainsi de suite. Si l’une des conditions est vraie,
la branche correspondante est exécutée et l’instruction se termine. Il est
fondamental de savoir que si plus d’une condition est vraie, seule la
première branche vraie sera exécutée ; si vous souhaitez exécuter les autres
aussi, utilisez simplement if.

Exceptions
Plus tôt, nous avons observé un segment de code dans lequel nous avons
utilisé les fonctions input et int() afin de lire et d’analyser un nombre entier
saisi par l’utilisateur. Nous avons aussi vu combien il pouvait être
dangereux de faire cela parce qu’une erreur pouvait être générée et il est
crucial de concevoir votre code afin de gérer les erreurs et les exceptions.
>>> question = 'Quel âge as-tu?\n'
>>> années = input(question)
Quel âge avez-vous?
test
>>> int(années)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'test'

Quand nous exécutons ces instructions dans l’interpréteur Python, nous


recevons un nouvelle prompt de l’interpréteur avec un traceback, cela
signifie une erreur. Si nous entrons ce code dans un script Python et que
cette erreur se produit, le script s’interrompt à l’instant même et n’exécute
pas l’instruction suivante. Afin que notre programme gère cette situation,
nous sommes en mesure d’utiliser la construction try...except.
Cette facilité d’exécution conditionnelle est intégrée à Python et a été
créée pour gérer les types d’erreurs attendues ou inattendues. L’idée est de
« tester » votre code en sachant que quelques séquences d’instructions
peuvent générer une erreur. Vous souhaitez alors ajouter des instructions à
exécuter au cas où l’erreur se produirait. Ces instructions extra (bloc
except) sont ignorées si aucune erreur ne se produit. Vous pouvez
considérer cette construction en Python comme une « police d’assurance »
sur une séquence d’instructions. Réécrivons le code précédent :
>>> question = 'Quel âge as-tu?\n'
>>> années = input(question)
Quel âge as-tu?
test
>>> try:
... int(années)
... except:
... print('Vous avez saisi une valeur non valide')
...
Vous avez saisi une valeur non valide

Python commence par exécuter la séquence d’instructions dans le bloc try


et si tout va bien, il saute le bloc except et continue. Si une exception se
produit dans le bloc try, Python saute hors de ce bloc et exécute la séquence
d’instructions dans le bloc except.
Quand nous traitons une exception avec une instruction try, nous
parlons d’attraper une exception. Dans cet exemple, la clause except
imprime un message d’erreur, mais généralement, le fait d’attraper une
exception vous offre l’occasion de résoudre le problème, de réessayer ou,
au moins, de terminer le programme sans erreur.
4

FONCTIONS

D ans le contexte de la programmation, une fonction est une séquence


d’instructions qui effectue un calcul. Quand vous définissez une
fonction, vous spécifiez le nom et la séquence d’instructions. Par la suite,
vous pouvez « appeler » la fonction par son nom pour effectuer à nouveau
ces calculs. Nous avons déjà observé un exemple d’appel à une fonction :
>>> type(32)
<class 'int'>

Le nom de la fonction est type alors que l’expression entre parenthèses est
nommée l’argument de la fonction. L’argument est une valeur ou une
variable que nous passons à la fonction comme input pour la fonction elle-
même. Le résultat, pour le fonction type, est le type de l’argument. Il est
commun de dire qu’une fonction « accepte » un argument et « renvoie » un
résultat, aussi nommé valeur de retour (ou valeur retournée).
Python donne un certain nombre de fonctions intégrées essentielles que
nous pouvons utiliser sans avoir besoin d’intégrer des bibliothèques ou de
les recréer. Les créateurs de Python ont écrit un certain nombre de fonctions
afin de résoudre des problèmes courants et les ont incluses dans Python
pour nous permettre de les utiliser. Les fonctions max et min, par exemple,
renvoient respectivement la plus grande et la plus petite valeur d’une liste
ou d’une chaîne de caractères :
>>> max('abcdefgh')
'h'
>>> min('abcdefgh')
'a'

La fonction max nous illustre le « plus grand caractère » de la chaîne (qui


s’avère être la lettre h) et la fonction min nous démontre le plus petit
caractère (qui s’avère être la lettre a). Une autre fonction intégrée vraiment
utile est la fonction len qui nous indique combien d’éléments il y a dans son
argument. Si l’argument de len est une chaîne de caractères, il renvoie le
nombre de caractères de la chaîne.
>>> len('test')
5

Il est important de noter que ces fonctions ne se limitent pas à travailler


avec des chaînes de caractères, elles peuvent en fait fonctionner sur
n’importe quel ensemble de valeurs. Vous devez considérer les noms des
fonctions intégrées comme des mots réservés (tentez d’éviter d’utiliser max
en tant que nom de variable).
Mais est-il possible de convertir une valeur d’un type à un autre ?
Python donne aussi des fonctions intégrées qui convertissent les valeurs
d’un type à un autre. La fonction int() accepte n’importe quelle valeur et la
convertit en un nombre entier si possible ; sinon, elle renvoie un message
d’erreur :
>>> int('32')
32
>>> int('Try')
ValueError: invalid literal for int() with base 10: 'Try'

De même, vous êtes en mesure d’utiliser float() et str() respectivement afin


de convertir une valeur en un nombre à virgule flottante ou afin d’obtenir
une chaîne de caractères à partir d’une valeur numérique.
>>> float(32)
32.0
>>> str(32)
'32'

Pour les mêmes input, la majorité des programmes pour computer génèrent
les mêmes output toutes les fois. Nous disons donc qu’ils sont
déterministes. Le déterminisme est en général une excellente chose
puisque nous nous attendons à ce que le même calcul produise le même
résultat à chaque fois. Pour quelques applications, cependant, nous voulons
que le computer soit imprévisible. Les jeux en sont un exemple évident,
mais il en existe d’autres, comme la cryptographie.
Il n’est pas si simple de rendre un programme réellement non
déterministe, mais il existe des moyens vraiment utiles. L’une d’entre elles
consiste à utiliser des algorithmes qui génèrent des nombres pseudo-
aléatoires. Les nombres pseudo-aléatoires ne sont pas vraiment aléatoires
parce qu’ils sont générés par un calcul déterministe, mais en observant de
tels nombres, il est presque impossible de les distinguer des nombres
aléatoires. Le module random donne des fonctions qui génèrent des
nombres pseudo-aléatoires. La fonction random renvoie un float aléatoire
entre 0,0 et 1,0 (y compris 0,0, mais pas 1,0). Toutes les fois que vous
appelez cette fonction, vous avez le numéro suivant dans une longue série.
Pour voir un exemple, exécutez cette boucle :

>>> import random


>>>
>>> for i in range(10):
... x = random.random()
... print(x)
...
0.052106790620197296
0.688074761676638
0.1162906504127218
0.29023807242781197
0.6002408949875643
0.4784974114988294
0.682726182433228
0.5958148653304466
0.37720316786047214
0.10726917045489204

Définir une fonction


Jusqu’à maintenant, nous n’avons utilisé que les fonctions données avec
Python, mais il est aussi possible d’ajouter de nouvelles fonctions. Une
définition de fonction spécifie le nom d’une nouvelle fonction et la
séquence d’instructions qui sont exécutées quand la fonction est appelée.
Une fois qu’une fonction est définie, nous sommes en mesure de la
réutiliser de nombreuses fois dans notre programme, ce qui évite de réécrire
le même code.
>>> nom = 'Antonio'
>>> def saluezUtilisateur(nom):
... print('Bienvenue ' + nom)
...
>>> saluezUtilisateur(nom)
Bienvenue Antonio

Comme vous l’avez probablement deviné, def est un mot clé indiquant une
définition de fonction et le nom de la fonction est saluezUtilisateur. Les
règles applicables aux noms de fonctions sont les mêmes que pour les noms
de variables : les lettres, les chiffres et quelques signes de ponctuation sont
autorisés, toutefois le premier caractère ne peut être un chiffre. Ainsi, vous
ne pouvez pas utiliser un mot-clé comme nom d’une fonction. De plus,
vous devez éviter d’avoir une variable et une fonction portant le même
nom.
Il est important de savoir que les parenthèses après le nom de la
fonction servent à indiquer les arguments de la fonction, si elles sont vides,
elles mentionnent que la fonction n’accepte aucun argument. Dans ce cas, la
fonction accepte un paramètre d’entrée, à savoir le nom de l’utilisateur à
saluer.
La première ligne de la définition de la fonction est nommée l’en-tête ;
le reste est nommé le corps. L’en-tête doit se terminer par un deux-points et
le corps du texte doit être indenté. Par convention, l’indentation est toujours
de quatre espaces. Le corps peut contenir un nombre quelconque
d’instructions. En fait, notez que si vous tapez une définition de fonction en
mode interactif, l’agent utilisateur imprime des ellipses (...) afin de vous
informer que la définition n’est pas complète. Afin de terminer la fonction,
vous devez saisir une ligne vide (ce n’est pas nécessaire dans un script).
Un aspect spécial de Python est que la définition d’une fonction crée
une variable du même nom, et la syntaxe pour appeler (ou invoquer) une
fonction que nous créons est la même que celle utilisée pour les fonctions
intégrées dans Python. Après avoir défini une fonction, il est possible de
l’utiliser dans une autre fonction. Par exemple, nous serions en mesure
d’écrire une fonction afin d’accueillir l’utilisateur et envoyer un code de
vérification pour l’accès. Observez l’exemple ci-dessous :
>>> def saluezUtilisateur(nom):
... print('Bienvenue ' + nom)
...
>>> def accèsSécurisé(nom):
... saluezUtilisateur(nom)
... print('Pin envoyé')
...
>>> accèsSécurisé(nom)
Bienvenue Antonio
Pin envoyé
Les définitions de fonction sont exécutées précisément de la même
façon que les autres instructions, toutefois elles ont pour effet de créer des
objets fonctionnels. Les instructions contenues dans la fonction ne sont pas
exécutées tant que la fonction n’est pas appelée et que la définition de la
fonction ne génère pas de output. Comme on peut s’y attendre, une fonction
doit être créée avant de pouvoir être exécutée, autrement dit, la définition de
la fonction doit être exécutée avant le premier appel.
Pour s’assurer qu’une fonction est définie avant sa première utilisation,
il est essentiel de connaître l’ordre dans lequel les instructions sont
exécutées. Cet ordre est en fait nommé flux d’exécution. L’exécution
commence toujours à la première instruction du programme et les
instructions sont exécutées une par une, dans l’ordre de haut en bas. Il est
important de noter que les définitions de fonction ne modifient pas le flux
d’exécution du programme, mais elles nous rappellent que les instructions
contenues dans la fonction ne sont pas exécutées tant que la fonction n’est
pas appelée.
En fait, un appel de fonction est comme une diversion dans le flux
d’exécution en ce sens qu’au lieu de passer à l’instruction suivante, le flux
saute au corps de la fonction, exécute l’ensemble des instructions du corps
de la fonction, puis reprend là où il s’est arrêté. Cela semble plutôt facile,
jusqu’à ce que vous vous souveniez qu’une fonction peut en appeler une
autre.
Au milieu d’une fonction, le programme peut devoir exécuter des
instructions dans une autre fonction, créant ainsi une boucle complexe.
Heureusement, Python sait garder la trace de l’endroit où il se trouve. Donc,
lorsqu’une fonction est terminée, le programme reprend là où il s’est arrêté
dans la fonction qui l’a appelée. Quand il atteint la fin du programme, le
programme se termine. Dans cette optique, quand vous lisez un programme,
ne vous contentez pas de le lire de haut en bas, il est quelquefois plus
judicieux de suivre le flux d’exécution.
Certaines des fonctions intégrées, ainsi que la fonction qui vient d’être
créée, nécessitent des arguments. Quelques fonctions acceptent aussi plus
d’un argument et, au sein de la fonction, ces arguments sont affectés à des
variables nommées paramètres.
Les paramètres sont évalués avant que la fonction ne soit appelée. Il est
important de clarifier un aspect très fondamental qui peut être source de
confusion, surtout pour les débutants. Le nom de la variable que nous
passons comme argument (nom) n’a rien à voir avec le nom du paramètre
(nom). Le nom de la valeur dans l’appelant n’a pas d’importance ; dans la
fonction, le paramètre peut avoir un nom différent. Changeons l’exemple
pour rendre le concept plus clair :
>>> nom = 'Antonio'
>>> def saluezUtilisateur(nom):
... print('Bienvenue ' + nom)
...
>>> def accèsSécurisé(nom_utilisateur):
... saluezUtilisateur(nom_utilisateur)
... print('Pin envoyè')
...
>>> accèsSécurisé(nom)
Bienvenue Antonio
Pin envoyé

Comme vous pouvez le constater dans l’exemple ci-dessus, nous avons


défini un nom de variable que nous transmettons comme input à la fonction
accèsSécurisé(). Le paramètre utilisé afin de définir accèsSécurisé() est
nom_utilisateur, mais il n’est pas indispensable de passer une variable avec
le même nom comme input.
La raison pour laquelle il est utile de diviser un programme en fonctions
n’est probablement pas évidente. Il y a, en fait, de nombreuses raisons:

La création d’une nouvelle fonction donne l’occasion d’identifier


un groupe d’instructions qui facilite la lecture, la compréhension et
le debug (identification des erreurs) du programme ;
Les fonctions peuvent aussi réduire la longueur d’un programme
en éliminant les codes répétés. Si vous avez des fonctions et que
vous apportez un changement, vous ne devez le faire qu’à un seul
endroit ;
La division d’un long programme en fonctions permet d'exécuter le
debug de les parties une à une, et par la suite de les assembler en
un seul ensemble fonctionnel ;
Des fonctions bien conçues sont très utiles pour plusieurs
programmes. Après avoir écrit les fonctions et effectué le debug,
vous pouvez les réutiliser.

Nous conseillons fortement l’utilisation des fonctions, et bien que vous


puissiez être quelque peu réticent à les utiliser, vous réalisez rapidement du
temps qu’elles peuvent vous faire gagner. Veillez à créer des fonctions dont
le nom correspond à leur tâche et, surtout, à ce qu’elles n’effectuent qu’une
seule tâche pour qu’elles soient atomiques et qu’elles n’aient pas à intégrer
de nombreuses tâches en leur sein.
5

ITÉRATIONS

L esrépétitives
computer sont fréquemment utilisés afin d’automatiser des tâches
sans commettre d’erreurs. Effectivement, c’est une tâche que
les computer accomplissent généralement bien, alors que les personnes la
font mal. Parce que l’itération est si courante, Python donne de nombreuses
fonctionnalités du langage pour la rendre plus simple. Une forme d’itération
en Python est l’instruction while. Voici un programme simple pour donner
le « Mark, we are rolling » après un compte à rebours.
n=3
while n > 0:
print(n)
n=n-1
print('Mark, we are rolling!')
Vous pouvez presque lire l’instruction while comme si c’était de
l’anglais. En fait, elle veut dire « Jusqu’à ce que n soit supérieur à 0,
affichez la valeur de n, puis réduisez la valeur de n d’une unité ». Quand
vous arrivez à 0, quittez l’instruction while et affichez la phrase "Mark, we
are rolling!".
Plus formellement, voici le flux d’exécution d’une instruction while :

1. Évaluez la condition, produisant vrai ou faux ;


2. Si la condition est fausse, nous quittons l’instruction while et nous
poursuivons l’exécution avec l’instruction suivante ;
3. Si la condition est vraie, exécutez le corps de la boucle, et ensuite
revenez à l’étape 1.
Ce type de flux est nommé loop (ou cycle), car le troisième point
revient à l’un des points précédents et que chaque fois que nous exécutons
le corps du cycle est nommé une itération. Pour le cycle ci-dessus, nous
dirions « Elle a eu trois itérations », ce qui veut dire que le corps de le cycle
a été exécuté trois fois.
Le corps de le cycle doit changer la valeur d’une ou de plusieurs
variables pour que la condition devienne fausse et que le cycle puisse se
terminer. La variable qui est changée chaque fois que le cycle est exécutée
et qui contrôle la fin de le cycle est appelée variable d’itération. Si aucune
variable d’itération n’est présente, le cycle se répétera à l’infini, générant
une boucle infinie.
Dans le cas du compte à rebours, nous pouvons démontrer que le cycle
se termine parce que nous savons que la valeur de n est finie, et nous
pouvons constater que la valeur de n diminue à chaque fois dans le cycle,
alors nous devons au final arriver à 0.
Parfois, une boucle est infinie parce qu’elle n’a pas de variables
d’itération. D’autres fois, vous ne savez pas qu’il est temps de terminer une
boucle avant d’avoir parcouru la moitié du corps de le cycle. Alors, dans ce
cas, vous pouvez écrire un loop infinie à dessein, et ensuite utiliser
l’instruction break pour sortir de la boucle. Nous créons une boucle infinie
dont il n’est pas possible de sortir :
n = 10
while True:
print(n, end=' ')
n=n-1
print('Cycle terminé')

Si vous commettez une erreur similaire ou exécutez ce code, vous


apprendrez sous peu la façon d’arrêter un processus Python sur votre
système ou vous devrez bientôt éteindre votre computer. Il est important de
noter que ce programme fonctionnera à l’infini ou jusqu’à ce que la batterie
de votre laptop soit épuisée, car l’expression logique en haut de la boucle
est toujours vraie, du fait que l’expression est la valeur constante True.
Même s’il s’agit d’une boucle infinie, nous pouvons toujours utiliser ce
modèle afin de créer des boucles utiles. Vous vous posez probablement la
question suivante : mais pourquoi utiliser True comme condition d’une
boucle ?
En fait, ce n’est pas tout à fait incorrect. En effet, il s’agit en fait d’une
pratique utilisée dans la programmation client-server. Si vous avez déjà créé
un server qui accepte les demandes en input des client, vous avez peut-être
déjà utilisé une boucle infinie pour que le server écoute sur un port en
attendant qu’un client envoie une demande.
Évidemment, cela ne peut pas durer éternellement, il est alors
fondamental de savoir comment sortir de la boucle au lieu d’arrêter
brutalement le server.

Cycle for
Quelquefois, nous souhaitons itérer sur un ensemble d’éléments comme une
liste de mots, les lignes de un file ou une liste de chiffres. Quand nous
disposons d’une liste définie d’éléments à parcourir par itération, nous
pouvons construire une boucle à l’aide d’une instruction for.
L’instruction while constitue une boucle indéfinie parce qu’elle itère
simplement jusqu’à ce qu’une condition devienne fausse, tandis que le
cycle for itère sur un ensemble d’éléments déjà connus de façon à parcourir
tous les éléments de l’ensemble. Effectivement, la syntaxe d’une boucle for
est similaire à celle de la boucle while en ce sens qu’elle possède un en-tête
et un corps de boucle:
utilisateurs = ['Antonio', 'Filippo', 'Marco']
for utilisateur in utilisateurs:
print('Bonjour: ', utilisateur)
print(' Bonjour à tous!')

En fait, la variable utilisateur, pour Python, est une liste de trois chaînes de
caractères et la boucle for parcourt la liste et exécute le corps de la boucle
une fois pour chacune des trois chaînes de caractères de la liste, ce qui
donne l’output illustré ci-dessous :
Bonjour: Antonio
Bonjour: Filippo
Bonjour: Marco
Bonjour à tous!

La traduction de ce for n'est pas aussi facile que dans le cas de while,
toutefois si vous considérez les utilisateurs comme un ensemble : "Exécutez
les instructions dans le corps de la boucle for une fois pour chaque
utilisateur de l’ensemble appelé utilisateurs." Les mots-clés for et in sont
réservés en Python alors que utilisateur et utilisateurs sont des variables que
nous créons.
Particulièrement, utilisateur est la variable d’itération de la boucle for.
Cette variable change pour chaque itération de la boucle et contrôle lorsque
la boucle est terminée. La variable d’itération parcourt successivement les
trois chaînes de caractères stockées dans la variable utilisateurs.
Nous utilisons fréquemment une boucle for ou while afin de parcourir
une liste d’éléments ou bien le contenu de un file et nous recherchons
quelque chose comme la plus grande ou bien la plus petite valeur dans les
données que nous parcourons. Ces boucles sont en général constituées de:

Initialisation d’une ou de nombreuses variables avant le début du


cycle ;
Effectuer un calcul sur chaque élément du corps de la boucle,
éventuellement en modifiant les variables du corps de la boucle ;
Évaluation des variables résultantes à la fin du cycle.
6

LISTES

C omme une chaîne de caractères, une liste (ou énumération) est une
séquence de valeurs. Dans une chaîne de caractères, les valeurs sont des
caractères. Il est important de noter que dans une liste, les valeurs peuvent
être de n’importe quel type et ces valeurs sont nommées éléments. Il existe
de nombreuses manières de créer une nouvelle liste ; la plus facile est de
mettre les éléments entre crochets :
[234, 212, 200, 123]
['Antonio', 'Filippo', 'Marco'].

Le premier exemple est une liste de quatre entiers alors que le second est
une liste de trois chaînes de caractères. Les éléments d’une liste peuvent
cependant ne pas être du même type. L’exemple ci-dessous illustre une liste
contenant une chaîne, un float, un entier et une autre liste:
['test', 6.3, 100, [10, 20]]

Nous avons créé une liste avec une liste à l’intérieur. En fait, cette liste peut
contenir des éléments ou être vide. Une liste qui ne contient aucun élément
est nommée liste vide ; vous êtes en mesure d’en créer une avec des
crochets vides [].
La syntaxe afin d’accéder aux éléments d’une liste est la même que
pour accéder aux caractères d’une chaîne de caractères, ce qui veut dire que
l’utilisation de l’opérateur crochet. L’expression entre parenthèses spécifie
l’index de l’élément que nous voulons sélectionner. Il faut se souvenir que
les index débutent par 0 :
>>> utilisateurs = ['Antonio', Filippo', 'Marco']
>>> print(utilisateurs[0])
Antonio

Contrairement aux chaînes de caractères, les listes sont mutables parce


qu’il est possible de changer l’ordre des éléments d'une même liste ou de
réaffecter un élément dans une liste. Dans ce cas, nous souhaitons modifier
le dernier élément de la liste des utilisateurs en le remplaçant par Giovanni:
>>> utilisateurs[2] = 'Giovanni'.
>>> print(utilisateurs)
['Antonio', 'Filippo', 'Giovanni']

Vous pouvez considérer une liste comme une relation entre les index et les
éléments. Cette relation est nommée "mappage" ; chaque indice "mappe"
l’un des éléments. Les indices d’une liste fonctionnent de la même façon
que les indices d’une chaîne de caractères :

Toute expression entière peut être utilisée comme index ;


Si vous tentez de lire ou bien d’écrire un élément qui n’existe pas,
vous obtenez une IndexError ;
Si un index a une valeur négative, les éléments sont récupérés à la
fin de la liste.

L’opérateur in fonctionne aussi dans les listes, comme vous pouvez le


constater dans l’exemple ci-dessous :
>>> utilisateurs = ['Antonio','Filippo', 'Marco']
>>> 'Antonio' in utilisateurs
True

>>> 'Mirko' in utilisateurs


False

Opérateurs et méthodes utiles


La manière la plus courante de faire défiler les éléments d’une liste est
d’utiliser une boucle for. La syntaxe est la même que pour les chaînes de
caractères :
for utilisateur in utilisateurs:
print(utilisateur)

Cette approche fonctionne bien si vous avez seulement besoin de lire les
éléments de la liste. Cependant, si vous souhaitez écrire ou mettre à jour les
éléments, vous avez besoin d'index. Une manière fréquente de contourner
ce problème est en fait de combiner la fonction len avec range:
numéros = [10, 25, 66].
for i in range(len(numéros)):
numéros [i] = numéros[i] * 2

Cette boucle parcourt la liste et met à jour chaque élément. La fonction len
renvoie le nombre d’éléments de la liste alors que range renvoie une liste
d’indices de 0 à n-1, où n est la longueur de la liste. À chaque fois, à travers
la boucle, nous obtenons l’indice du prochain élément. L’instruction
d’affectation dans le corps de la boucle utilise i pour lire l’ancienne valeur
de l’élément et pour affecter la nouvelle valeur.
Vous pouvez vraiment tout exécuter avec les listes. Par exemple, vous
pouvez les concaténer avec l’opérateur +, vous pouvez aussi répéter des
éléments avec l’opérateur * ou même les "couper" avec l’opérateur deux
points (:).

>>> a = [1, 2, 3]
>>> b = [4, 5, 6]
>>> c = a + b
>>> print(c)
[1, 2, 3, 4, 5, 6]

>>> [0] * 4
[0, 0, 0, 0]
>>> [1, 2, 3] * 2
[1, 2, 3, 1, 2, 3]

>>> t = ['a', 'b', 'c', 'd', 'e', 'f']


>>> t[1:3]
['b', 'c']
>>> t[:4]
['a', 'b', 'c', 'd']
>>> t[3 :]
['d', 'e', 'f']
Python donne aussi des méthodes qui opèrent sur des listes. Par
exemple, append() ajoute un nouvel élément à la fin d’une liste :
>>> t = ['a', 'b', 'c']
>>> t.append('d')
>>> print(t)
['a', 'b', 'c', 'd']

La méthode extend(), quant à elle, prend en input une liste et ajoute


l’ensemble de ses éléments à la liste sur laquelle elle a été invoquée :
>>> t1 = ['a', 'b', 'c']
>>> t2 = ['d', 'e']
>>> t1.extend(t2)
>>> print(t1)
['a', 'b', 'c', 'd', 'e']
Notez qu’une autre méthode largement utilisée est sort(), qui permet de trier
les éléments dans l’ordre croissant, ce qui veut dire du plus petit au plus
grand :
>>> t = ['d', 'c', 'e', 'b', 'a']
>>> t.sort()
>>> print(t)
['a', 'b', 'c', 'd', 'e']

Tout comme il est possible d’ajouter des éléments à une liste, il est aussi
possible de les supprimer avec les méthodes pop(), del et remove().
Si vous connaissez l’index de l’élément à retirer, vous pouvez utiliser
pop() qui change la liste et renvoie l’élément retiré. Si vous n’avez pas
besoin du dernier élément retiré, vous pouvez utiliser del, alors que si vous
connaissez la valeur à retirer, mais pas sa position, vous pouvez utiliser
remove(). Observons-les en action dans l’exemple ci-dessous:
>>> t = ['a', 'b', 'c']
>>> x = t.pop(1)
>>> print(t)
['a', 'c']
>>> print(x)
b

>>> t = ['a', 'b', 'c']


>>> del t[1]
>>> print(t)
['a', 'c']

>>> t = ['a', 'b', 'c']


>>> t.remove('b')
>>> print(t)
['a', 'c']
>>> t = ['a', 'b', 'c', 'd', 'e', 'f']
>>> del t[1:5]
>>> print(t)
['a', 'f']

En fait, il existe un certain nombre de fonctions intégrées qui peuvent être


utilisées dans les listes et qui permettent de consulter de façon rapide une
liste sans réécrire une boucle :
>>> numéros = [3, 41, 12, 9, 74, 15]
>>> print(len(numéros))
6
>>> print(max(numéros))
74
>>> print(min(numéros))
3
>>> print(sum(numéros))
154
>>> print(sum(numéros)/len(numéros))
25

Il est important de noter que la fonction sum() ne fonctionne que lorsque les
éléments de la liste sont des nombres, tandis que les autres fonctions
(max(), len(), etc.) fonctionnent aussi avec des listes de chaînes de
caractères et d’autres types qui peuvent être comparables.
7

DICTIONNAIRES

U nlesdictionnaire est comme une liste, mais plus générale. Dans une liste,
positions d’index doivent être des entiers tandis que dans un
dictionnaire, les index peuvent être (presque) de n’importe quel type. En
fait, vous pouvez considérer un dictionnaire comme une correspondance
entre un ensemble d’index (nommés clés) et un ensemble de valeurs, où
chaque clé est associée à une valeur.
L’association d’une clé et d’une valeur est nommée paire clé-valeur ou
parfois élément. Par exemple, nous allons créer un dictionnaire qui sera en
mesure de faire correspondre des chiffres anglais à des chiffres italiens, les
clés et les valeurs seront donc toutes des chaînes de caractères. La fonction
dict crée un nouveau dictionnaire sans éléments, et comme dict est le nom
d’une fonction intégrée, il est recommandé d’éviter de l’utiliser comme
nom de variable.
>>> engToit = dict()
>>> print(engToit)
{}

Notez que de cette manière, nous avons créé un dictionnaire vide, puisque
les accolades sans valeurs à l’intérieur mentionnent de manière précise un
dictionnaire sans association. Nous pouvons ajouter des éléments comme
suit :
>>> engToit['one'] = 'un'
>>> print(engToit)
{'one' : 'un'}

Nous avons créé un élément qui est mappé de la clé one à la valeur un. En
imprimant à nouveau le dictionnaire, nous observons une paire clé-valeur
avec deux points séparant la clé de la valeur.
Tentons d’ajouter d’autres paires au dictionnaire :
>>> engToit = {'one' : 'un', 'two' : 'deux', 'three': 'trois'}
>>> print(engToit)
{'one': 'un', 'three': 'trois', 'two' : 'deux'}

L’ordre des paires clé-valeur, comme vous l’avez probablement observé,


n’est pas le même que lorsque nous les avons saisies. En fait, si vous tapez
le même exemple sur votre computer, vous obtiendrez probablement un
résultat différent. Généralement, l’ordre des éléments d’un dictionnaire est
imprévisible, mais cela ne pose pas de problème parce que les éléments
d’un dictionnaire ne sont jamais indexés avec des indices entiers. Chaque
élément est accessible à l’aide de la clé correspondante :
>>> print(engToit ['two'])
'deux'
Si nous tentons de nous référer à un élément inexistant dans le
dictionnaire, nous obtiendrons un KeyError que nous serons en mesure de
gérer avec un bloc try...except.
Comme pour les listes, nous pouvons utiliser les fonctions len() afin
d’obtenir le nombre d’éléments du dictionnaire et le mot clé in afin de
vérifier si un élément est présent dans le dictionnaire.
>>> len(engToit)
3
>>> 'one' in engToit
True
>>> 'un' in engToit
False
Une utilisation classique des dictionnaires consiste à compter combien de
fois un mot ou une lettre est répété dans un texte. Il existe en fait de
nombreuses manières de procéder :

1. Vous pourriez créer 26 variables, une pour chaque lettre de


l’alphabet. Vous pourriez itérer sur le texte et, pour chacun des
caractères, incrémenter le compteur correspondant ;
2. Il est également possible de créer une liste comportant 26 éléments.
Par la suite, vous pouvez convertir chacun des caractères en un
nombre (en utilisant la fonction intégrée ord()), en utilisant le
nombre comme index dans la liste et en incrémentant le compteur
approprié ;
3. Vous êtes en mesure de créer un dictionnaire avec des caractères
comme clés et des compteurs comme valeurs correspondantes. La
première fois que vous rencontrez un personnage, vous ajoutez un
objet au dictionnaire. Si vous avez déjà rencontré ce caractère,
vous augmentez la valeur de la clé correspondante.

Il est important de noter que chacune de ces options effectue le même


calcul, mais chacune met en œuvre ce calcul différemment. Une
implémentation est une manière d’effectuer un calcul, mais quelques
implémentations sont meilleures que d’autres. Par exemple, un avantage de
l’implémentation du dictionnaire est que nous n’avons pas besoin de savoir
à l’avance quelles lettres apparaissent dans la chaîne de caractères et que
nous ne cherchons pas l’ensemble des lettres, mais utilisons seulement
celles qui apparaissent réellement dans le texte. Voici ci-dessous à quoi
pourrait ressembler un code exploitant les dictionnaires:
text = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam
ac elit eget augue tempus lacinia. Quisque mattis odio tincidunt rutrum
placerat'
d = dict()

for c in text:
if c not in d:
d[c] = 1
else:
d[c] = d[c] + 1

print(d)

En fait, nous calculons un histogramme, qui est un terme statistique


désignant un ensemble de compteurs (ou de fréquences). Il est important de
noter que la boucle for itère la chaîne de caractères et toutes les fois que
nous passons par la boucle, si le caractère c n'est pas dans le dictionnaire,
nous créons un nouvel élément avec la clé c et la valeur initiale 1 (puisque
nous n’avons vu ce caractère qu’une seule fois). Si c est déjà dans le
dictionnaire, nous incrémentons d[c].
8

ENSEMBLES

L esquiensembles (aussi nommés set) incarnent un concept mathématique


est fréquemment utilisé en programmation quand un groupe unique
d’éléments est requis. En Python, les ensembles sont vraiment similaires
aux dictionnaires avec des clés, mais sans valeurs correspondantes. Les set
ont le type de Python set. Les mêmes règles de base s’appliquent que pour
les clés de dictionnaire, à savoir que les valeurs des ensembles doivent être
immuables et uniques (en fait, c’est ce qui en fait un ensemble !).
La valeur par défaut de set() est en fait la collection vide et c’est la seule
manière d’exprimer une collection vide, car {} est déjà utilisé pour un
dictionnaire vide. En effet, la fonction set() accepte tout type de collection
comme argument et la convertit en un ensemble (les valeurs des
dictionnaires seront perdues). De plus, il existe un autre type de set en
Python, nommé frozenset, qui est immuable et qui est essentiellement un
set en lecture seule. Son constructeur fonctionne comme set(), mais ne
prend en charge qu’un sous-ensemble des opérations. Vous pouvez
employer un frozenset comme élément d’un set standard, car le frozenset
est immuable. Notez que les littéraux d’ensembles sont exprimés avec des
crochets entourant les éléments séparés par des virgules :
myset = {1,2,3,4,5}

Les set ne supportent pas l’indexation ou le slicing et, comme les


dictionnaires, n’ont pas d’ordre inhérent. La fonction sorted() renvoie une
liste ordonnée de valeurs.
Les ensembles ont de nombreuses opérations sur les ensembles de style
mathématique qui sont en fait distincts des autres collections. Le tableau ci-
dessous affiche les opérations communes aux types set et frozenset :
Opération
Description
in
Cela aide à vérifier si un seul élément existe dans le set. Si l’élément
testé est lui-même un ensemble, S1, le résultat ne sera vrai que si S1, en tant
qu’ensemble, est en fait un élément de l’ensemble cible.
issubset, <=, <
Ils testent si un ensemble est un sous-ensemble de la destination, ce qui
veut dire si l’ensemble des membres de l’ensemble testé sont aussi
membres de la destination. Si les ensembles sont identiques, le test
retournera True dans les deux premiers cas, mais False pour l’opérateur <.
issuperset, >=, >
Ils vérifient si un set est un superset de l’autre, cela veut dire que si tous
les membres de le set cible sont des membres de le set source. S’ils sont
égaux, les deux premières opérations renvoient True; la dernière opération
renvoie False.
union, |
Ils retournent l’union de deux (ou plus) ensembles. La méthode union
accepte une liste d’ensembles séparés par des virgules comme arguments,
alors que l’opérateur pipe est utilisé de façon fixe entre les ensembles.
intersection, &
Ils retournent l’ensemble d’intersection de deux (ou plus) ensembles.
L’utilisation est semblable à celle de union().
difference, -
Ils retournent les membres de le set source qui, en fait, ne sont pas
membres de le set cible.
symmetric_difference, ^
Renvoie les éléments des deux ensembles qui ne sont
pas dans l’intersection. Cette méthode ne fonctionne que pour deux
ensembles, toutefois l’opérateur ^ peut être appliqué à de nombreux
ensembles.
Remarquez que les variations de la méthode dans le tableau accepteront
n’importe quel type de collection comme argument tandis que les
opérations infixes ne fonctionneront qu’avec les ensembles.
9

PROGRAMMES PYTHON

L es(parprogrammes Python n’ont pas de point d’entrée par défaut obligatoire


exemple, une fonction main()) et sont en fait simplement exprimés
sous forme de code source dans un file de texte qui est lu et exécuté dans
l’ordre depuis le début du file. Il est important de noter que les définitions,
comme les fonctions, sont exécutées dans le sens où la fonction est créée et
porte un nom, toutefois le code interne n’est jamais exécuté tant que la
fonction n’est pas invoquée. Python n’a pas de syntaxe spéciale pour
indiquer si un file source est un programme ou bien un module, et, comme
vous l’observerez, un file donné peut aussi être utilisé dans les deux rôles.
Un file de programme exécutable typique se compose d’un ensemble
d’instructions import permettant d’insérer l’ensemble des modules de code
nécessaires, certaines définitions de fonctions et de classes, et un peu de
code directement exécutable. En pratique, pour un programme non trivial,
la majorité des définitions de fonctions et de classes existeront dans les file
de modules et seront incluses dans les import. Il ne reste alors qu’une courte
section du code du driver pour lancer l’application. Souvent, ce code sera
placé dans une fonction et la fonction sera souvent appelée main(), toutefois
il s’agit d’un simple clin d’œil à la convention de programmation, et non
d’une exigence de Python.
Finalement, cette fonction "main" doit être appelée. Cela se fait
fréquemment dans une instruction if spéciale à la fin du script principal et
ressemble à ceci :
if __name__ == "__main__":
main()
Quand Python détecte qu’un file de programme est exécuté par
l’interpréteur plutôt qu’importé en tant que module, il donne à la variable
spéciale __ name__ (remarquez les doubles traits de soulignement des deux
côtés) la valeur " __ main__ ". Cela veut dire que tout le code contenu dans
ce bloc if n’est exécuté que lorsque le script est exécuté en tant que
programme principal et non lorsque le file est importé d’un autre
programme. Si le file ne doit être utilisé que comme module, la fonction
main() peut être remplacée par une fonction test() qui exécute une série de
unit test. Encore une fois, le nom réel employé n’a aucune signification
pour Python.
La structure de programmation la plus fondamentale est une séquence
d’instructions. Normalement, les instructions Python se situent sur une ligne
à part ; par conséquent, une séquence est tout simplement une série de
lignes.
x=2
y = 12
z = 135

Dans cet exemple ci-dessus, les déclarations sont toutes des affectations.
Les autres déclarations valables sont les appels de fonctions, les
importations de modules ou les définitions. Les définitions comprennent les
fonctions et les classes. Les diverses structures de contrôle décrites dans les
sections suivantes s’appliquent aussi.
Python est un langage structuré par blocs et les blocs de code sont
affichés par le niveau d’indentation. La quantité d’indentation est assez
flexible, et bien que la majorité des programmeurs Python s’en tiennent à
l’utilisation de trois ou quatre espaces afin d’optimiser la lisibilité, cela ne
fait aucune différence en Python. Les divers environnements de
développement intégrés (nommés IDE) et editor de texte ont leurs propres
idées sur la façon d’effectuer l’indentation. Si plusieurs outils de
programmation sont utilisés, des erreurs d’indentation peuvent être
signalées parce que les outils ont utilisé différentes combinaisons de
tabulations et d’espaces au fil du temps.
Si vous êtes en mesure de le faire, configurez votre editor pour qu’il
utilise des espaces. Les commentaires constituent l’exception à la règle de
l’indentation. Un commentaire Python commence par le symbole # et se
poursuit jusqu’à la fin de la ligne. Python accepte les commentaires qui
commencent n’importe où sur la ligne, quel que soit le niveau d’indentation
actuel. Cependant, par convention, les programmeurs ont tendance à
maintenir le niveau d’indentation, même pour les commentaires. Python
supporte un ensemble limité d’options de sélection. La structure la plus
élémentaire est la construction if/elif/else que nous avons déjà analysée.
Il est fréquemment possible de rencontrer des erreurs dans les
programmes ; il existe deux approches de la détection des erreurs en
Python. La première consiste à vérifier explicitement chaque action au fur
et à mesure qu’elle est exécutée. Tandis que l’autre tente des opérations et
s’appuie sur le système afin de générer une condition d’erreur, ou
exception, si quelque chose ne va pas. Même si la première approche est
appropriée dans certaines situations, il est beaucoup plus fréquent dans
Python d’utiliser la seconde. Python supporte cette technique avec la
construction try/except/else/finally. Dans sa forme générale, il ressemble à
ceci :
try:
A block of application code
except <an error type> as <anExceptionObject>:
A block of error handling code
else:
Another block of application code
finally:
A block of clean-up code

Notez que except, else et finally sont tous facultatifs, bien qu’au moins un
except ou finally doive exister si une instruction try est utilisée. Il peut y
avoir plus d’une clause except, mais une seule clause else ou finally. Il est
possible d’omettre la partie as… d’une ligne d’instruction except si les
détails de l’exception ne sont pas obligatoires.
Le bloc try est exécuté et, si une erreur se produit, la classe d’exception
est testée. Si une instruction except existe pour ce type d’erreur, le bloc
correspondant est exécuté. S’il existe de nombreux blocs d’exception
spécifiant le même type d’exception, seule la première clause
correspondante est exécutée. Dans le cas où aucune instruction except
correspondante n’est trouvée, l’exception est propagée vers le haut jusqu’à
ce que l’interpréteur de niveau supérieur soit atteint et que Python génère le
rapport d’erreur de traceback habituel.
Remarquez qu’une instruction except vide permet de détecter n’importe
quel type d’erreur ; toute, c’est en général une mauvaise idée parce qu’elle
masque l’apparition de toute erreur inattendue.
Le bloc else est exécuté si le bloc try réussit sans erreur ; en pratique,
else est rarement utilisé. Qu’une erreur soit détectée ou propagée, ou que la
clause else soit exécutée, la clause finally sera toujours exécutée, donnant
alors la possibilité de libérer toute ressource de calcul dans un état bloqué.
C’est aussi le cas quand la clause try/except est liée à une instruction break
ou return.
Il est possible d’utiliser une seule instruction except afin de traiter
plusieurs types d’exception. Vous pouvez le faire en énumérant les classes
de except dans un tuple (les parenthèses sont obligatoires). L’objet
exception contient les détails de l’endroit où l’exception s’est produite et
donne une méthode de conversion de la chaîne de caractères de sorte qu’un
message d’erreur significatif puisse être donné en imprimant l’objet. Vous
pouvez aussi lever des exceptions à partir de votre code. Vous pouvez aussi
utiliser l’un des types d’exception existants ou définir les vôtres en créant
une sous-classe de la classe Exception. Vous pouvez aussi passer des
arguments aux exceptions levées et vous pouvez y accéder dans l’objet
exception de la clause exclude en utilisant l’attribut args de l’objet error.
Voici ci-dessous un exemple de génération d’une ValueError standard avec
un argument personnalisé, et ensuite de détection de cette erreur et
d’impression de l’argument spécifié.
>>> try:
... raise ValueError('wrong value')
... except ValueError as error:
... print (error.args)
...
('wrong value',)
Remarquez que vous n’avez pas obtenu un traceback complet, mais
seulement l’output imprimée du bloc except. Vous pouvez aussi relancer
l’exception originale après le traitement en appelant simplement raise sans
arguments.
Finalement, discutons du concept de contexte de runtime en Python. Il
s’agit en général d’une ressource temporaire quelconque avec laquelle votre
programme veut interagir. Un exemple typique serait un file ouvert ou un
thread d’exécution concurrent. Afin de gérer cela, Python utilise le mot-clé
with et un protocole de gestion du contexte. Ce protocole vous aide à
définir vos propres classes de gestion de contexte, mais vous utiliserez
principalement les gestionnaires donnés par Python. Vous utilisez un
gestionnaire de contexte en invoquant l’instruction with :
with open(filename, mode) as contextName:
process file here

Le gestionnaire de contexte s’assure que le file est fermé après utilisation.


Ceci est assez typique du rôle d’un gestionnaire de contexte : s’assurer que
les ressources sont libérées après utilisation ou que les précautions de
partage appropriées sont prises pendant la première utilisation. Les
gestionnaires de contexte éliminent souvent la nécessité d’une construction
try/finally. Le module contextlib donne un support pour créer vos propres
gestionnaires de contexte.
Vous avez donc maintenant exploré les divers types de données que
Python peut traiter, ainsi que les structures de contrôle que vous pouvez
utiliser afin d’effectuer ce traitement. Il est maintenant temps de découvrir
comment avoir des données depuis et vers vos programmes Python.

Input et output
L’input et l’output de données de base sont une exigence essentielle de tout
langage de programmation. Vous devez tenir compte de la façon dont vos
programmes interagiront avec les utilisateurs et les données stockées dans
les file. Afin d’envoyer des données aux utilisateurs via stdout, vous utilisez
la fonction print(), que vous avez déjà vue de nombreuses fois. Vous
apprendrez comment contrôler l’output de manière plus précise dans cette
section.
Afin de lire les données des utilisateurs, nous utilisons la fonction
input(), qui demande un input à l’utilisateur et renvoie par la suite une
chaîne de caractères bruts à partir de stdin. La fonction print() est plus
complexe qu’il n’y paraît à première vue parce qu’elle comporte de
nombreux paramètres facultatifs. À son niveau le plus facile, vous passez
tout simplement une chaîne de caractères et print() l’affiche sur stdout suivi
d’un caractère de fin de ligne (eol). Le niveau de complexité suivant
implique le passage de données autres que des chaînes de caractères, que
print() convertit ensuite en chaîne de caractères (à l’aide de la fonction de
type str()) avant d’afficher le résultat. En augmentant un peu la complexité,
vous pouvez passer de nombreux éléments à la fois à print(), il les
convertira et les affichera séparés par un espace. Voici ci-dessous trois
éléments fixes dans le comportement de print() :

Affiche l’output sur stdout ;


Il se termine par un caractère eol ;
Séparez les éléments par un espace.

En fait, aucune d’entre elles n’est vraiment correcte et print() vous


permet de changer tout ou partie d’entre elles en utilisant des paramètres
optionnels. Vous pouvez changer l’output en spécifiant un argument file; le
séparateur est défini par l’argument sep et le caractère de fin est défini par
l’argument end. La ligne ci-dessous imprime le fameux message "hello
world", spécifié sous forme de deux chaînes, dans un file en utilisant un
trait d’union comme séparateur et la chaîne "END" comme terminateur:
with open("tmp.txt", "w") as tmp:
print("Hello", "World", end="END", sep="-", file=tmp)

Le résultat est: "Hello-WorldEND".


La méthode format() de string devient vraiment unique quand elle est
combinée avec print(). Cette combinaison permet de présenter vos données
de façon claire et nette, en les séparant les unes des autres. En fait,
l’utilisation de format() peut fréquemment être plus efficace que de tenter
d’imprimer une longue liste d’objets et de fragments de chaîne. Il existe
plusieurs exemples d’utilisation de format() dans la documentation Python.
Vous pouvez aussi communiquer avec l’utilisateur en utilisant la
fonction input(), qui lit les valeurs tapées par l’utilisateur en réponse à un
prompt donnée sur l’écran. Vous devez vous souvenir qu’il est de votre
responsabilité de convertir les caractères renvoyés en n’importe quel type
de données représenté et de traiter toute erreur résultant de cette conversion.
Voici un exemple qui demande à l’utilisateur de saisir un nombre. Il est
important de savoir que si le nombre est trop élevé ou trop faible, il
imprime un avertissement:

target = 66
while True :
value = input("Enter an integer between 1 and 100")
try:
value = int(value)
break
except ValueError:
print("I said enter an integer!")
if value > target:
print (value, "is too high")
elif int(value) < target:
print("too low")
else:
print("Pefect")

Dans cet exemple, l’utilisateur est invité à saisir un nombre entier dans la
plage appropriée. La valeur lue est ensuite convertie en un nombre entier à
l’aide de int(). Si la conversion échoue, une exception ValueError est levée
et le message d’erreur est indiqué. Si la conversion est réussie, vous êtes
donc en mesure d’interrompre la boucle while et procéder au test par
rapport au target, en sachant que vous avez un entier valide.
Les file de texte sont les chevaux de bataille de la programmation quand
il s’agit de sauvegarder des données, et Python prend en charge de
nombreuses fonctions afin de gérer les file de texte. Vous avez vu la
fonction open() dans les sections précédentes, qui prend un nom de file et
un mode comme arguments.
Le mode peut être l’un des suivants: r, w, rw et a pour lecture, écriture,
lecture-écriture et ajout respectivement. Il existe aussi plusieurs autres
modes utilisés moins fréquemment ainsi que certains paramètres optionnels
qui contrôlent la manière dont les données sont interprétées, voir la
documentation pour plus de détails.
Le mode r demande que le file existe ; les modes w et rw créent un
nouveau file vide (ou écrasent tout file existant avec le même nom). Le
mode a ouvre un file existant ou crée un nouveau file vide si un file avec le
nom de file spécifié n’existe pas déjà. L’objet file retourné est aussi un
gestionnaire de contexte et donc peut être utilisé dans un bloc with comme
vous l’avez vu dans la section précédente. Si un bloc with n’est pas utilisé,
vous devez fermer explicitement le file en utilisant la méthode close()
quand vous avez fini de travailler dessus, ce qui garantit que l’ensemble des
données contenues dans les buffer de mémoire sont envoyées au file
physique sur le disque. La construction with appelle automatiquement
close(), ce qui est l’un des avantages de l’utilisation de l’approche du
gestionnaire de contexte. Une fois que vous avez ouvert un objet file, vous
êtes en mesure d’utiliser read(), readlines() ou readline() selon vos besoins.
La fonction read() lit l’intégralité du contenu du file sous la forme d’une
chaîne unique, avec les caractères affichant le début d’une nouvelle ligne,
readlines() lit ligne par ligne dans une liste et les caractères de la nouvelle
ligne sont conservés, readline() lit la ligne suivante du file, en réservant la
nouvelle ligne. L’objet file est itérable, vous pouvez alors l’utiliser
directement dans une boucle for sans avoir besoin des méthodes de lecture.
Le modèle conseillé afin de lire les lignes dans un file est le modèle illustré
ci-dessous:
with open(filename, mode) as fileobject:
for line in fileobject:
# process line

Il est possible d’écrire dans un objet file à l’aide des méthodes write() ou
writelines(), qui sont les équivalents des méthodes read portant des noms
similaires. Remarquez qu’il n’existe pas de méthode writeline() afin
d’écrire une seule ligne. Si vous utilisez le mode rw, vous pouvez vous
déplacer vers un emplacement spécifique du file afin d’écraser les données
existantes. Vous êtes en mesure de trouver votre position actuelle dans le
file en utilisant la méthode tell(), et vous pouvez vous déplacer vers une
position spécifique (éventuellement celle que vous avez enregistrée avec
tell() précédemment) en utilisant la méthode seek(). La fonction seek() a de
nombreuses façons de calculer la position ; la valeur par défaut est
simplement l’offset au début du file.
Vous disposez maintenant de l’ensemble des compétences de base pour
écrire des programmes Python fonctionnels. Cependant, afin de vous
attaquer à des projets plus essentiels, vous devrez étendre vos compétences
en Python.
10

EXTENSION DES PROGRAMMES

L apropres
façon la plus facile d’étendre l’utilisation de Python est d’écrire vos
fonctions. Vous êtes en mesure de les définir dans le même file
que le code qui les utilise, ou vous pouvez créer un nouveau module et
importer des fonctions depuis celui-ci. Pour le moment, vous allez créer les
fonctions et les utiliser dans le même file, en fait, vous pourriez surtout
utiliser le prompt interactive pour les exemples de cette section. L’étape
suivante de la création de récentes fonctions en Python consiste à définir
vos propres classes et à créer des objets à partir de celles-ci. Les exemples
illustrés ici sont suffisamment faciles pour que vous puissiez simplement
utiliser le prompt Python.
Les programmeurs Python utilisent fréquemment des chaînes de
documentation dans leurs programmes. Les chaînes de documentation sont
des chaînes littérales qui ne sont pas affectées à une variable et qui
respectent le niveau d’indentation auquel elles sont définies. Les chaînes de
documentation sont utilisées afin de décrire les fonctions, les classes ou les
modules. La fonction help() lit et affiche les chaînes de documentation.

Fonctions
Plusieurs types de fonctions sont disponibles en Python. Vous êtes en
mesure de définir des fonctions en Python à l’aide du mot-clé def. Le
formulaire ressemble à ce qui est affiché ci-dessous:
def functionName(parameter1, param2,...):
function block
Notez que les fonctions Python renvoient toujours une valeur. Vous pouvez
spécifier une valeur de retour explicite en employant le mot-clé return;
sinon, Python renvoie None par défaut. (Si vous verrez que des valeurs
None inattendues apparaissent dans l’output, vérifiez que la fonction en
question comporte une déclaration return explicite dans son corps). Vous
pouvez fournir des valeurs par défaut aux paramètres en faisant suivre le
nom d’un signe égal et de la valeur.
Dans ce cas, nous créons une nouvelle fonction qui accepte de
nombreux paramètres de input et renvoie une valeur. Cette fonction utilise
aussi l’équation mathématique d’une ligne afin de retourner la coordonnée
y correspondante pour un gradient, une coordonnée x et une constante
donnés. Nous allons alors utiliser la fonction afin de générer un ensemble
de coordonnées pour une ligne.

1. Lancez l’interpréteur Python.


2. Entrez le code suivant pour définir la fonction:

>>> def straight_line(gradient, x, constant):


... ''' returns y coordinate of a straight line
-> gradient * x + constant'''
... return gradient*x + constant
...
>>>

1. Maintenant que vous avez défini la fonction, essayez-la en utilisant


quelques valeurs simples que vous êtes en mesure de calculer dans
votre tête. Tentez d'appeler la fonction avec un gradient de 2, une
valeur x de 4 et une constante -3 :

>>> # test with a single value first


>>> straight_line(2,4,‐3)
5
1. Tentons maintenant un test plus difficile de la fonction, en utilisant
le code ci-dessous:

>>> for x in range(10):


... print(x,straight_line(2,x,-3))
...
0 -3
1 -1
21
33
45
57
69
7 11
8 13
9 15

1. Finalement, vérifiez que la fonction help() reconnaît correctement


la fonction :

>>> help(straight_line)
Help on function straight_line in module __main__:
straight_line(gradient, x, constant)
returns y coordinate of a straight line
-> gradient * x + constant
(END)

Dans la première ligne de l’étape 2, nous avons créé la définition de la


fonction. Nous l’avons nommé straight_line et avons affiché qu’il avait
trois paramètres requis : gradient, x et constante. Ces paramètres
correspondent aux valeurs utilisées dans l’équation mathématique y =
mx+c, où m est le gradient et c est une constante. La deuxième ligne est une
chaîne de documentation qui décrit à quoi sert la fonction et la façon dont
elle doit être utilisée. La troisième ligne est le bloc de code de fonction.
Il peut être arbitrairement difficile et comporter plusieurs lignes, mais
dans le cas présent, il s’agit d’une seule ligne et vous renvoyez le résultat ;
vous lui avez donc ajouté le mot clé return. Remarquez que la ligne de code
doit commencer au même niveau d’indentation que le début de la chaîne de
documentation ; sinon, vous recevrez une erreur d’indentation. Vous avez
par la suite testé la fonction avec certaines valeurs simples, certains calculs
mentaux confirment que la valeur de retour 5 est bien égale à (2*4-3). La
fonction semble fonctionner, du moins pour un cas facile. La fonction a
ensuite été utilisée pour générer un ensemble de paires de coordonnées x,y
en utilisant une boucle for avec une valeur fixe pour le gradient (2) et une
constante (-3), mais en fournissant la variable de la boucle comme x. Si
vous avez du papier à portée de main, vous pouvez tenter de dessiner les
coordonnées affichées afin de confirmer qu’elles forment une ligne droite.
Finalement, vous avez utilisé la fonction help() afin de confirmer que la
chaîne de documentation a été détectée et affichée correctement.
Les fonctions génératrices sont précisément comme les fonctions
standard, sauf qu’au lieu d’utiliser return afin de retourner une valeur, elles
utilisent le mot clé yield. (En théorie, ils peuvent utiliser return en plus de
yield, mais seules les expressions yield produisent le comportement typique
des générateurs). La partie intéressante est que ces fonctions se comportent
comme un appareil photo. Quand une fonction standard atteint une
instruction return, elle renvoie la valeur et la fonction se débarrasse de
toutes ses données internes. La prochaine fois que la fonction est appelée,
tout recommence à zéro.
La déclaration yield fait quelque chose de distinct. Il renvoie une valeur,
comme le fait return, mais il n’entraîne pas la suppression des données de la
fonction ; au contraire, l’ensemble des données est préservé. L’appel de
fonction suivant reprend l’instruction yield, même s’il se situe au milieu
d’un bloc ou d’une boucle. Il peut aussi y avoir plusieurs déclarations yield
dans une seule fonction, puisque la déclaration yield peut être à l’intérieur
d’une boucle, il est possible de créer une fonction qui renvoie réellement
une série infinie de résultats. Voici un exemple qui renvoie une série
croissante de nombres impairs:
def odds(start=1):
''' return all odd numbers from start upwards'''
if int(start) % 2 == 0: start = int(start) + 1
while True:
yield start
start += 2

Dans cette fonction, nous vérifions tout d’abord que l’argument de départ
est un nombre entier impair (un nombre entier pair divisé par 2 a un reste
nul), et si ce n’est pas le cas, nous le forçons à être le nombre entier impair
immédiatement supérieur en ajoutant 1 unité. Nous créons par la suite une
boucle infinie de type while. Normalement, c’est une mauvaise idée parce
que votre programme va se planter à tout jamais. Toutefois, comme il s’agit
d’une fonction de générateur, vous utilisez yield afin de renvoyer la valeur
de départ, de sorte que la fonction se termine à ce moment-là, en renvoyant
la valeur de départ à ce moment-là. La prochaine fois que la fonction est
appelée, l’exécution commence exactement là où vous vous êtes arrêté. Par
la suite, start est incrémenté de 2 unités et la boucle s’exécute à nouveau,
produisant le prochain nombre impair et quittant la fonction jusqu’à la
prochaine fois. Python fait en sorte que les fonctions génératrices
deviennent des itérateurs pour qu’elles puissent être utilisées dans des
boucles for, comme ceci :
for n in odds():
if n > 7: break
else: print(n)

Nous utilisons odds() comme une collection. Chaque fois que le loop est
exécutée, elle appelle la fonction générateur et reçoit la prochaine valeur
impaire. Remarquez que nous évitons une boucle infinie en insérant le test
d’interruption pour ne jamais appeler odds() au-delà de 7. Maintenant que
vous comprenez le fonctionnement des fonctions de générateur, vous avez
probablement réalisé que les expressions de générateur illustrées plus tôt
dans ce chapitre ne sont en fait que des fonctions de générateur anonymes.
Les expressions de générateur sont en fait une forme déguisée d’une
fonction de générateur sans nom. Cela constitue une transition parfaite vers
le dernier type de fonction que nous allons apprendre ici : la fonction
lambda.
Le terme lambda provient d’une forme de calcul mathématique inventée
par Alonzo Church. La excellente nouvelle, c’est qu’il n’est pas nécessaire
de s’y connaître en mathématiques afin d’utiliser les fonctions lambda !
L’idée derrière une fonction lambda est qu’il s’agit d’un bloc de fonction
anonyme, généralement vraiment petit, que vous pouvez insérer dans votre
code, qui appelle par la suite la fonction lambda comme une fonction
normale. Les fonctions lambda ne sont pas vraiment répandues, mais elles
sont très utiles quand vous devez créer de nombreuses petites fonctions qui
ne sont utilisées qu’une seule fois. Ils sont fréquemment utilisés dans des
contextes de programmation GUI ou de réseau, où le toolkit de
programmation exige qu’une fonction soit appelée avec un résultat. Une
fonction lambda est définie de la façon suivante:
lambda <param1, param2, ...,paramN> : <expression>

Il s’agit du mot lambda littéral, suivi d’une liste facultative de noms de


paramètres séparés par des virgules, de deux points et d’une expression
Python arbitraire qui emploie habituellement les paramètres d’input.
Remarquez que l’expression ne comporte pas le mot return devant elle.
Quelques langages permettent aux fonctions lambda d’être arbitrairement
complexes ; cependant, Python vous limite à une seule expression.
L’expression peut devenir assez difficile, mais en pratique, il est mieux de
créer une fonction standard si nécessaire parce qu’elle sera plus facile à lire
et à exécuter le debug si les choses tournent mal. Il est possible d’attribuer
des fonctions lambda à des variables, auquel cas ces variables ressemblent
précisément aux noms des fonctions Python standard. Par exemple, voici la
fonction straight_line réimplémentée comme fonction lambda:
>>> straight_line = lambda m,x,c: m*x+c
>>> straight_line(2,4,‐3)
5

Souvenez-vous qu’il s’agit simplement d’une façon concise d’exprimer une


fonction courte, à expression unique.
Classes
Python prend en charge la programmation orientée objet en utilisant une
approche traditionnelle basée sur les classes. Les classes Python supportent
l’héritage multiple et l’overloading d’opérateurs (mais pas l’overloading de
méthodes), ainsi que les mécanismes normaux d’encapsulation et de
passage de messages.
Les classes Python n’implémentent pas directement le masquage des
données (data hiding), même si certaines conventions de nommage sont
utilisées afin de donner un mince niveau de privacy pour les attributs et afin
de suggérer lorsque les attributs ne doivent pas être utilisés directement par
les client. Les méthodes et les données des classes sont prises en charge,
tout comme les concepts de propriétés et de slot. Les classes possèdent à la
fois un constructeur ( __new__ ()) et un initialisateur ( __init__ ()), ainsi
qu’un mécanisme de destruction ( __del__ ()), bien qu’il ne soit pas
toujours garanti que ce dernier soit invoqué.
Les classes servent de namespace pour les méthodes et les données
définies, alors que les objets sont des instances de classes. Une classe est
définie à l’aide du mot-clé class suivi du nom de la classe et d’une liste de
superclasses entre parenthèses. La définition de la classe possède un
ensemble de données de classe et de définitions de méthodes. Une
définition de méthode a comme premier paramètre une référence à
l’instance appelante, traditionnellement nommée self. Une définition de
classe simple ressemble à ce qui est illustrée ci-dessous:

class MyClass(object):
instance_count = 0
def __init__(self, value):
self.__value = value
MyClass.instance_count += 1
print("instance No {} created".format(MyClass.instance_count))
def aMethod(self, aValue):
self.__value *= aValue
def __str__(self):
return "A MyClass instance with value: " + str(self.__value)
def __del__(self):
MyClass.instance_count -= 1

Le nom de la classe commence traditionnellement par une majuscule. Dans


Python 3, la superclasse est toujours object, sauf indication contraire, alors
l’utilisation d’object comme superclasse dans l’exemple ci-dessus est en
effet redondante. L’élément de données instance_count est un attribut de
classe parce qu’il n’apparaît dans aucune des méthodes de la classe. La
fonction __init__() est l’initialisateur ; en fait, les constructeurs sont
rarement utilisés en Python, sauf s’ils héritent d’une classe intégrée. Elle
définit la valeur de la variable d’instance self.__ value, incrémente la
variable de classe définie précédemment instance_count, ensuite imprime
un message. Le double soulignement devant la valeur (__) affiche qu’il
s’agit bien de données privées et qu’elles ne doivent pas être utilisées
directement. La méthode __init__() est automatiquement appelée par
Python tout de suite après la construction de l’objet. La méthode d’instance
aMethod() modifie l’attribut d’instance créé dans la méthode __init__().
La méthode __str__() est une méthode spéciale utilisée afin de renvoyer
une chaîne formatée afin que les instances transmises à la fonction print, par
exemple, soient imprimées de façon significative. Le destructeur __del__()
décrémente le nombre d’instances de la variable de classe instance_count
quand l’objet est détruit.
Vous pouvez créer une instance de la classe de cette façon :
myInstance = MyClass(42)
Cela crée une instance en mémoire et appelle ensuite
MyClass.__init__() avec la nouvelle instance comme self et 42 comme
valeur. Vous pouvez appeler la méthode aMethod() en utilisant la dot
notation par points de cette façon :
myInstance.aMethod(66)

Cela se traduit par l’invocation la plus explicite :


MyClass.aMethod(myInstance, 66)
et entraîne le comportement souhaité, à savoir l’ajustement de la valeur de
l’attribut __value. Vous pouvez constater la méthode __str__() à l’œuvre si
vous imprimez l’instance, comme ceci:
print(myInstance)

Le message devrait s’afficher :


A MyClass instance with value: 2772
Vous pouvez aussi imprimer la valeur de instance_count avant et après
la création/destruction d’une instance :
print(MyClass.instance_count)
inst = MyClass(44)
print(MyClass.instance_count)
del(inst)
print(MyClass.instance_count)

Cela devrait démontrer que le compte est incrémenté ensuite décrémenté à


nouveau, il peut y avoir un léger délai avant l’appel du destructeur, mais
cela ne devrait durer que certains instants.
Les méthodes __init__(), __del__() et __str__() ne sont pas les seules
méthodes spéciales. Il en existe beaucoup d’autres, toutes reconnaissables à
l’utilisation du double soulignement (parfois nommé méthode dunder). Il
est important de noter que l’overloading des opérateurs est prise en charge
par un certain nombre de ces méthodes spéciales, notamment : __add__ (),
__sub__ (), __mul__ (), __div__ () et ainsi de suite. D’autres méthodes
impliquent la mise en œuvre de protocoles Python comme l’itération ou la
gestion du contexte. Vous pouvez surcharger ces méthodes dans vos classes,
mais vous ne devez jamais définir vos propres méthodes complexes ; sinon,
les améliorations futures de Python pourraient compromettre l’exécution du
code. Vous pouvez remplacer les méthodes dans les sous-classes et les
nouvelles définitions sont en mesure d’appeler la version héritée de la
méthode en utilisant la fonction super(), comme ceci :
class SubClass(Parent):
def __init__(self, aValue) :
super().__init__(aValue)
L’appel à super().__init__() entraîne un appel à la méthode __init__() de
Parent. L’utilisation de super() permet d’éviter les soucis, notamment en cas
d’héritage multiple, quand une classe peut être héritée plusieurs fois, vous
ne voulez en général pas qu’elle soit initialisée plus d’une fois.
Les slot sont un dispositif de stockage de la mémoire et sont appelés en
utilisant l’attribut spécial __slot__ et en donnant une liste de noms
d’attributs d’objets. Les __slot__ sont souvent une optimisation prématurée
et vous ne devriez les utiliser que si vous avez un besoin spécifique. Notez
que les propriétés sont une autre fonctionnalité disponible pour les attributs
de données et vous permettent de rendre un attribut en lecture seule (ou
même en écriture seule) en forçant l’accès par le biais d’un ensemble de
méthodes, même si la syntaxe habituelle des méthodes n’est pas utilisée.
Voici un exemple dans lequel vous créez une classe Circle avec un attribut
rayon et une méthode area(). Vous voulez que la valeur du rayon soit
toujours positive, et vous ne voulez pas que les client puissent la changer en
une valeur négative. Vous souhaitez aussi que area ressemble à un attribut
de données en lecture seule, même si elle est mise en œuvre comme une
méthode. Vous êtes en mesure d’atteindre ces deux objectifs en créant des
propriétés pour le rayon et area.
Nous débutons par créer une classe simple, Circle1, qui ne possède
qu’un attribut et deux méthodes appelables: setRadius() et area(). Créez
ensuite une deuxième classe, Circle2, qui crée les propriétés de rayon et
area. Finalement, nous observerons comment l’utilisation des propriétés
simplifie l’utilisation de la classe dans le code client.

1. Lancez votre editor de programmes ou votre IDE préféré et créez


un nouveau file nommé testCircle.py.
2. Entrez le code suivant:

class Circle1:
def __init__(self, radius):
self.__radius = radius
def setRadius(self,newValue):
if newValue >= 0:
class Circle1:
def __init__(self, radius):
self.__radius = radius
def setRadius(self,newValue):
if newValue >= 0:
self.__radius = newValue
else: raise ValueError("Value must be positive")
def area(self):
return 3.14159 * (self.__radius ** 2)
class Circle2:
def __init__(self, radius):
self.__radius = radius
def __setRadius(self, newValue):
if newValue >= 0:
self.__radius = newValue
else: raise ValueError("Value must be positive")
radius = property(None, __setRadius)
@property
def area(self):
return 3.14159 * (self.__radius ** 2)

1. Enregistrer le code
2. Lancez l’interpréteur Python et tapez le code ci-dessous pour
utiliser Circle1 :

>>> import testCircle as tc


>>> c1 = tc.Circle1(42)
>>> c1.area()
5541.76476
>>> print(c1.__radius)
Traceback (most recent call last):
File "<interactive input>", line 1, in <module>
AttributeError: 'Circle1' object has no attribute '__radius'
>>> c1.setRadius(66)
>>> c1.area()
13684.766039999999
>>> c1.setRadius(‐4)
Traceback (most recent call last):
File "<interactive input>", line 1, in <module>
File "D:\PythonCode\Chapter1\testCircle.py", line 7, in setRadius
else: raise ValueError("Value must be positive")
ValueError: Value must be positive

1. Nous utilisons Circle2 avec le code suivant:

>>> c2 = Circle2(42)
>>> c2.area
5541.76476
>>> print(c2.radius)
Traceback (most recent call last):
File "<interactive input>", line 1, in <module>
AttributeError: unreadable attribute
>>> c2.radius = 12
>>> c2.area
452.38896
>>> c2.radius = ‐4
Traceback (most recent call last):
File "<interactive input>", line 1, in <module>
File "D:\PythonCode\Chapter1\testCircle.py", line 18, in __setRadius
else: raise ValueError("Value must be positive")
ValueError: Value must be positive
>>>

Notez que dans testCircle.py, vous avez créé deux classes. En effet, la
première, Circle1, réalise ce que vous souhaitiez faire en forçant
l’utilisateur à changer la valeur du rayon via la méthode setRadius(). Vous
avez fait cela en préfixant l’attribut self.__radius avec deux underscores, ce
qui est la manière dont Python le rend privé. Vous avez par la suite créé la
méthode setRadius(), qui valide la valeur fournie avant de l’appliquer et
génère une erreur si une valeur négative est trouvée. Vous avez aussi donné
une méthode area() afin que l’utilisateur puisse évaluer la zone en utilisant
la technique habituelle d’appel de méthode. La deuxième classe, Circle2,
aborde les choses de façon assez différente puisqu’elle utilise la fonction de
définition de propriété de Python pour créer un attribut appelé radius qui est
en écriture seule. Il a aussi créé la méthode area comme un attribut en
lecture seule. Cela a rendu le code utilisateur de Circle2 beaucoup plus
intuitif, comme vous avez pu le voir en utilisant le code dans l’interpréteur.
La clé se trouve dans la fonction property() que vous avez appelée de la
manière suivante :
radius = property(None, __setRadius)

Ce code accepte comme arguments un ensemble de fonctions de lecture,


d’écriture et de suppression (ainsi qu’une chaîne de documentation). La
valeur par défaut de chacun d’eux est None. Dans ce cas, vous avez créé la
propriété radius avec une fonction de lecture None, mais avec la méthode
(maintenant privée) __setRadius() comme fonction d’écriture. Les autres
valeurs ont été laissées à la valeur par défaut None. Donc, le résultat était
que l’utilisateur pouvait accéder à radius comme s’il s’agissait d’un attribut
de données publiques lorsqu’il attribuait une valeur, mais, implicitement,
Python appelait la méthode __setRadius(). Toute tentative de lecture (ou de
suppression) de l’attribut serait ignorée, car l’action serait dirigée vers
None. La propriété area est un peu différente et utilise un décorateur de
propriété Python (@property), qui est juste un raccourci afin de créer une
propriété en lecture seule. Il s’agit d’une utilisation vraiment courante des
propriétés. En observant la session, vous avez créé une instance Circle1 et
imprimé l’aire en utilisant la méthode area(). Vous avez par la suite tenté
d’imprimer le rayon directement en accédant à __radius, mais Python a
prétendu qu’il ne possédait pas cet attribut (en raison du paramètre de
double underscore privé) et a signalé une AttributeError. Quand vous avez
utilisé la méthode setRadius(), tout s’est bien déroulé et l’impression de la
zone une seconde fois a montré que la modification a fonctionné.
Finalement, vous avez tenté de définir un rayon négatif et, comme prévu, la
méthode a généré une exception ValueError avec un message d’erreur
personnalisé.
Dans la session utilisant Circle2, vous pouvez constater à quel point le
code est plus facile. Il évalue tout simplement le nom de la propriété area
afin d’obtenir la zone et attribue une valeur au nom de la propriété radius.
Quand nous essayons d’attribuer une valeur négative, la méthode génère à
nouveau un ValueError. L’impression directe du rayon génère à nouveau un
AttributeError, bien que le message soit cette fois légèrement différent. Les
propriétés nécessitent un petit effort extra de la part du programmeur, mais
peuvent grandement simplifier l’utilisation de la classe.
Après avoir observé la manière d’étendre les capacités de Python à
l’aide de fonctions et de classes, la prochaine section illustre la façon
d’encapsuler ces extensions dans des modules et des packages pour faciliter
leur réutilisation.

Modules et package
Les modules sont essentiels dans la majorité des environnements de
programmation pour les tâches de programmation non triviales.
Effectivement, ils permettent de diviser les programmes en blocs gérables et
donnent un mécanisme de réutilisation du code entre les projets.
En Python, les modules sont tout simplement des file sources, se
terminant par .py et situés à un endroit où Python peut les trouver. En
pratique, cela veut dire que le file doit se situer dans le directory de travail
actuel ou dans un dossier répertorié dans la variable sys.path. Notez que
vous pouvez ajouter vos dossiers à ce chemin en les spécifiant dans la
variable d’environnement PYTHONPATH de votre système ou
dynamiquement au moment de l’exécution. Même si les modules
constituent un moyen utile de regrouper de petites quantités de code source
en vue de leur réutilisation, ils ne sont pas totalement satisfaisants pour les
projets de plus grande envergure comme framework GUI ou les
bibliothèques de fonctions mathématiques. Pour cela, Python fournit le
concept de paquet ou package. Un paquet est principalement un dossier
rempli de modules. L’unique exigence est que le dossier possède un file
nommé __init__.py, qui peut être vide. Pour l’utilisateur, un paquet
ressemble beaucoup à un module, et les sous-modules du paquet peuvent
ressembler à des attributs du module.
Accédez aux modules en utilisant le mot-clé import, qui a beaucoup de
variantes en Python. Les formes les plus courantes sont illustrées ci-
dessous:
import aModule
import aModule as anAlias
import firstModule, secondModule, thirdModule...
from aModule import anObject
from aModule import anObject as anAlias
from aModule import firstObject,secondObject, thirdObject...
from aModule import *

Le dernier module importe tous les noms visibles de aModule dans le


namespace actuel. Cela présente un risque réel de créer des conflits de noms
avec des noms intégrés ou des noms que vous avez définis, ou que vous
allez définir, localement. Il est alors recommandé d’utiliser uniquement le
dernier module d’importation afin de tester les modules à le prompt de
Python. La saisie extra nécessaire afin d’utiliser les autres modules n’est
qu’un faible prix à payer par rapport à la confusion qui peut résulter d’un
conflit de noms. Les autres from… sont beaucoup plus sûrs parce que vous
importez uniquement les noms spécifiés et, si nécessaire, vous les
renommez avec un alias, ce qui rend les conflits avec d’autres noms locaux
beaucoup moins probables.
Après avoir importé un module à l’aide de l’une des trois premières
formes, nous sommes en mesure d’accéder à son contenu en plaçant le nom
requis avant le nom du module (ou l’alias) en utilisant la notation par points
(dot notation). Vous en avez déjà vu des exemples dans les sections
précédentes ; par exemple, sys.path est un attribut du module sys. Après
avoir mentionné que les modules sont tout simplement des file sources, en
pratique, observons ce qu’il faut faire et ne pas faire pendant la création de
modules.
Vous devez éviter le code de premier niveau qui sera exécuté pendant
que le module sera importé, sauf éventuellement pour quelques
initialisations de variables qui peuvent dépendre de l’environnement local.
Cela veut dire que le code que vous voulez réutiliser doit être emballé
comme une fonction ou une classe. Il est aussi possible de donner une
fonction de test qui exécute l’ensemble des fonctions et classes du module.
Les noms de modules sont également traditionnellement uniquement en
minuscules.
De plus, vous pouvez contrôler la visibilité des objets dans votre
formulaire de deux manières. La première est similaire au mécanisme de
privacy utilisé dans les classes, dans la mesure où vous pouvez préfixer un
nom avec un caractère de soulignement. Notez que de tels noms ne seront
pas exportés quand une instruction est utilisée : from x import *. L’autre
façon de contrôler la visibilité est de lister uniquement les noms que vous
voulez exporter dans une variable de niveau supérieur nommée __all__, ce
qui garantit que seuls les noms que vous voulez spécifiquement exporter
sont effectivement exportés et est conseillé par rapport à la méthode
précédente si la visibilité est essentielle pour vous.
Vous avez découvert au début de cette section qu’un paquetage Python
est tout simplement un dossier contenant un file nommé __init__.py. Tous
les autres file Python contenus dans ce dossier sont les modules du
paquetage. Python traite les packages comme un autre type de module, ce
qui signifie que les packages Python peuvent contenir d’autres packages en
leur sein avec une profondeur arbitraire, à condition que chaque sous-
package ait aussi son propre file __init__.py. Le file __init__.py lui-même
n’est pas particulièrement spécial ; c’est simplement un autre file Python et
il sera chargé quand vous importerez le paquetage. Cela veut dire que le file
peut être vide, auquel cas l’importation du paquetage donne tout
simplement accès aux modules inclus, ou qu’il peut contenir du code
Python comme tout autre module. En particulier, il peut définir une liste
__all__, comme décrit ci-dessus, afin de contrôler la visibilité, permettant
effectivement à un paquet d’avoir des file d’implémentation privés qui ne
sont pas exportés lorsqu’un client importe le paquet.
Une exigence courante pendant la création d’un paquet est d’avoir
inclus un ensemble de fonctions ou de données partagées entre tous les
modules. Notez que vous pouvez y parvenir en plaçant le code partagé dans
un file de module appelé, par exemple, common.py au niveau supérieur du
paquetage et en faisant en sorte que l’ensemble des autres modules importe
le code partagé. Il sera visible pour eux en tant que partie du paquet, mais
s’il n’est pas explicitement inclus dans la liste __all__, les utilisateurs
externes utilisant les paquets ne le verront pas. Quand vous utilisez un
paquet, traitez-le comme n’importe quel autre module. L’ensemble des
styles d’importation habituels est disponible, mais le schéma de nommage
est étendu en utilisant la notation par points pour spécifier les sous-modules
nécessaires dans la hiérarchie des paquets. Quand vous importez un sous-
module en utilisant la dot notation, vous définissez en fait deux nouveaux
noms : le paquet et le module. Supposez, par exemple, la déclaration
suivante:
import os. path

Ce code ci-dessus importe le sous-module du chemin du paquetage os, mais


rend aussi visible l'ensemble du module du système d'exploitation. Vous
êtes en mesure d’accéder aux fonctions de os sans avoir une déclaration
d’importation séparée pour os lui-même. Une des implications est que
Python exécute automatiquement tous les file __init__.py qu’il trouve dans
la hiérarchie de les file importés. Ainsi, dans le cas de os.path, il exécute
os.__init__.py puis path.__init__.py. Cependant, si vous utilisez un alias
après l’importation, comme ci-dessous :
import os.path as pth

seul le module os.path est exposé. Si vous souhaitez utiliser les fonctions du
module os, vous aurez besoin d’une importation explicite. Bien que seul le
chemin soit exposé, en tant que nom de pth, les file os et path__init__.py
seront toujours exécutés. La bibliothèque standard de Python contient de
nombreux paquets, dont le paquet os que nous venons de mentionner. Parmi
les autres, mentionnons les framework d’interface utilisateur tkinter et
curses, le paquet email et les paquets urllib, http et html axés sur le Web.
Maintenant que vous avez lu la théorie, il est temps de la mettre en
pratique. Dans cette section, nous allons créer certains modules et les
regrouper dans un paquet. L’intention est de donner une interface
fonctionnelle pour les opérateurs logiques et d’étendre leur champ d’action
pour inclure le test de valeurs individuelles de bit. De cette manière, vous
verrez aussi de nombreuses caractéristiques du langage Python principal qui
ont été abordées ci-dessus. Les modules développés ne sont pas optimisés
pour la performance, mais sont conçus pour illustrer les concepts.
Cependant, il ne serait pas difficile de les affiner pour en faire des outils
réellement utiles. Nous débutons par créer un module simple et
conventionnel basé sur input entières, par la suite nous créons un autre
module qui définit une classe pouvant être utilisée afin de représenter un
morceau de données binaires et exposer des fonctions bit à bit comme
méthodes. Finalement, nous aurons besoin d’un paquet contenant les deux
modules.

1. Créez un dossier appelé bitwise qui sera éventuellement votre


package.
2. Dans ce dossier, créez un script Python nommé bits.py contenant le
code ci-dessous:

#! /bin/env python3
''' Functional wrapper around the bitwise operators.
Designed to make their use more intuitive to users not
familiar with the underlying C operators.
Extends the functionality with bitmask read/set operations.
The inputs are integer values and
return types are 16 bit integers or boolean.
bit indexes are zero based
Functions implemented are:
NOT(int) -> int
AND(int, int) -> int
OR(int,int) -> int
XOR(int, int) -> int
shiftleft(int, num) -> int
shiftright(int, num) -> int
bit(int,index) -> bool
setbit(int, index) -> int
zerobit(int,index) -> int
listbits(int,num) -> [int,int...,int]
'''
def NOT(value):
return ~value
def AND(val1,val2):
return val1 & val2
def OR(val1, val2):
return val1 | val2
def XOR(val1,val2):
return val1^val2
def shiftleft(val, num):
return val << num
def shiftright(val, num):
return val >> num
def bit(val,idx):
mask = 1 << idx # all 0 except idx
return bool(val & 1)
def setbit(val,idx):
mask = 1 << idx # all 0 except idx
return val | mask
def zerobit(val, idx):
mask = ~(1 << idx) # all 1 except idx
return val & mask
def listbits(val):
num = len(bin(val)) - 2
result = []
for n in range(num):
result.append( 1 if bit(val,n) else 0 )
return list( reversed(result) )

1. Sauvegardez le file et, en restant dans le dossier bitwise, lancez


l’interpréteur Python.
2. Entrez le code ci-dessous afin de tester le module:

>>> import bits


>>> bits.NOT(0b0101)
-6
>>> bin(bits.NOT(0b0101))
'-0b110'
>>> bin(bits.NOT(0b0101) & 0xF)
'0b1010'
>>> bin(bits.AND(0b0101, 0b0011) & 0xF)
'0b1'
>>> bin(bits.AND(0b0101, 0b0100) & 0xF)
'0b100'
>>> bin(bits.OR(0b0101, 0b0100) & 0xF)
'0b101'
>>> bin(bits.OR(0b0101, 0b0011) & 0xF)
'0b111'
>>> bin(bits.XOR(0b0101, 0b11) & 0xF)
'0b110'
>>> bin(bits.XOR(0b0101, 0b0101) & 0xF)
'0b0'
>>> bin(bits.shiftleft(0b10,1))
'0b100'
>>> bin(bits.shiftleft(0b10,4))
'0b100000'
>>> bin(bits.shiftright(0b1000,2))
'0b10'
>>> bin(bits.shiftright(0b1000,6))
'0b0'
>>> bits.bit(0b0101,0)
True
>>> bits.bit(0b0101,1)
False
>>> bin(bits.setbit(0b1000,1))
'0b1010'
>>> bin(bits.zerobit(0b1000,1))
'0b1000'
>>> bits.listbits(0b10111)
[1, 0, 1, 1, 1]

Le module est une liste assez simple de fonctions qui encapsulent les
opérateurs bit à bit pour not (~) et (&), or (|), xor (^), shift left (<<) et shift
right (>). Notez que ces opérations fonctionnent sur des données binaires,
ce qui veut dire simplement qu’une séquence de 1 et de 0 stockée comme
une unité dans le computer. L’ensemble des données de le computer est, par
définition, stocké sous forme binaire. Ces opérations wrapper sont
complétées par une série de fonctions qui vérifient si un bit a la valeur 1
(c’est ce qu’on nomme "set"), mettent un bit (à 1) ou mettent un bit à zéro
(c’est ce qu’on nomme "reset"). Le nombre de bit est compté à partir de la
droite, en commençant par zéro. Les test sont effectués à l’aide d’un motif
de bit (aussi nommé masque de bit) qui, dans tous les cas sauf pour
zerobit(), est constitué de tous les zéros, à l’exception du bit que vous
voulez tester ou définir. Vous avez créé le masque en déplaçant 1 à gauche
du nombre de bit requis. La fonction zerobit() utilise le complément bit à bit
du masque habituel afin d’en créer un composé de tous les 1 sauf un 0 où
vous souhaitez réinitialiser le bit. Finalement, vous avez une fonction qui
liste les différents bit de la valeur donnée. Cette dernière fonction est
légèrement plus complexe et montre quelques des fonctionnalités de
Python.
Vous déterminez tout d’abord la longueur du nombre en le convertissant
en chaîne binaire avec bin() et en soustrayant 2 (pour tenir compte des
caractères 0b initiaux). Par la suite, vous créez une liste de résultats vide et
faites défiler les bit. Il est important de noter que pour chaque bit, vous
ajoutez 1 ou 0, selon que le bit est activé ou non, en utilisant la construction
d’expression conditionnelle de Python.
Le test du module soulève certaines questions intéressantes. Débutons
par importer votre nouveau module. Comme vous vous trouvez dans le
dossier où se trouve le file, Python peut le voir sans modifier la valeur
sys.path. Vous commencez à tester avec la fonction NOT() (en mode
préfixe, bien sûr, avec le nom du module, bits), et vous constatez tout de
suite une anomalie dans la mesure où l’interpréteur Python imprime la
représentation décimale comme résultat. Afin de contourner ce problème,
vous pouvez utiliser la fonction bin() pour convertir le nombre en une
représentation binaire de la chaîne de caractères. Toutefois, il est important
de savoir qu’il y a toujours un problème, car le nombre est négatif. En effet,
les entiers Python sont signés, c’est-à-dire qu’ils peuvent représenter des
nombres positifs ou négatifs. Python fait cela en interne en faisant en sorte
que le bit le plus à gauche représente le signe. En inversant tous les bit,
vous inversez aussi le signe ! Vous pouvez éviter ce problème en utilisant
un masque de bit de 0xF (ou 15 en décimal si vous préférez) pour ne
récupérer que les 4 bit les plus à droite. En convertissant ceci avec bin(),
vous observez maintenant le modèle de bit inversé que vous attendiez. Il est
évident que si la valeur que vous inversez est supérieure à 16, vous devez
utiliser un masque de bit plus long. Il est important de se souvenir que
chaque chiffre hexadécimal est composé de 4 bit. Il vous suffit alors
d’ajouter un F supplémentaire à votre masque.
Le prochain set de test, couvrant les fonctions AND() jusqu’à shiftleft(),
devrait être simple et vous pouvez vérifier les résultats en inspectant
visuellement les modèles de bit de input et les résultats. Les exemples avec
shiftright() illustrent un résultat intéressant en ce sens que décaler les bit
trop loin vers la droite produit un résultat nul. En d’autres mots, Python
remplit l’espace "vide" laissé par les opérations de décalage avec des zéros.
Concernant la nouvelle fonctionnalité, vous avez utilisé bit(), setbit() et
zerobit() afin de tester et modifier les bit individuels dans une valeur
donnée. Une fois de plus, vous pouvez inspecter visuellement les modèles
de input et de résultat afin de vérifier que les bons résultats sont produits.
Vous devez vous souvenir que le paramètre index compte à partir de zéro en
partant de la droite.
Finalement, vous avez testé la fonction listbits(). Une fois encore, vous
pouvez facilement comparer le modèle de input binaire avec la liste de
nombres qui en résulte. Vous avez maintenant un module fonctionnel que
vous pouvez importer et utiliser comme n’importe quel autre module en
Python. Notez que vous pouvez encore améliorer le module en fournissant
une fonction de test et en l’enfermant dans une clause if __name__ si vous
le voulez, mais pour l’instant vous pouvez trouver comment passer d’un
module unique à un paquet. Créez une classe qui reproduit les fonctions en
bits.py sous la forme d’un ensemble de méthodes ; ensuite, regroupez les
deux modules dans un package.

1. Parcourez le dossier bitwise


2. Créez un nouveau file nommé bitmask.py avec le code suivant :

#! /bin/env python3
''' Class that represents a bit mask.
It has methods representing all
the bitwise operations plus some
additional features. The methods
return a new BitMask object or
a boolean result. See the bits
module for more on the operations
provided.
'''

class BitMask(int):
def AND(self,bm):
return BitMask(self & bm)
def OR(self,bm):
return BitMask(self | bm)
def XOR(self,bm):
return BitMask(self ^ bm)
def NOT(self):
return BitMask(~self)
def shiftleft(self, num):
return BitMask(self << num)
def shiftright(self, num):
return BitMask(self > num)
def bit(self, num):
mask = 1 << num
return bool(self & mask)
def setbit(self, num):
mask = 1 << num
return BitMask(self | mask)
def zerobit(self, num):
mask = ~(1 << num)
return BitMask(self & mask)
def listbits(self, start=0,end=-1):
end = end if end < 0 else end+2
return [int(c) for c in bin(self)[start+2:end]]

1. Maintenant, enregistrez-la pour pouvoir la tester dans l’interpréteur


Python
2. En restant dans le dossier bitwise, lancez Python et tapez le code
ci-dessous :
>>> import bitmask
>>> bm1 = bitmask.BitMask()
>>> bm1
0
>>> bin(bm1.NOT() & 0xf)
'0b1111'
>>> bm2 = bitmask.BitMask(0b10101100)
>>> bin(bm2 & 0xFF)
'0b10101100'
>>> bin(bm2 & 0xF)
'0b1100'
>>> bm1.AND(bm2)
0
>>> bin(bm1.OR(bm2))
'0b10101100'
>>> bm1 = bm1.OR(0b110)
>>> bin(bm1)
'0b110'
>>> bin(bm2)
'0b10101100'
>>> bin(bm1.XOR(bm2))
'0b10101010'
>>> bm3 = bm1.shiftleft(3)
>>> bin(bm3)
'0b110000'
>>> bm1 == bm3.shiftright(3)
True
>>> bm4 = bitmask.BitMask(0b11110000)
>>> bm4.listbits()
[1, 1, 1, 1, 0, 0, 0]
>>> bm4.listbits(2,5)
[1, 1, 0]
>>> bm4.listbits(2,‐2)
[1, 1, 0, 0]
1. Fermez l’interpréteur, maintenant que vous avez prouvé que le
nouveau module fonctionne, vous pouvez aller de l’avant et
convertir la directory bitwise en un paquetage Python.
2. Créez un file vide __init.py__
3. Afin de vérifier que le paquetage fonctionne, vous devez modifier
votre directory de travail en celui qui est au-dessus bitwise. Vous
devez maintenant vérifier que vous pouvez importer le paquet et
son contenu et accéder à la fonctionnalité.
4. Lancez l’interpréteur Python et tapez le code de test ci-dessous:

>>> import bitwise.bits as bits


>>> from bitwise import bitmask
>>> bits
<module 'bitwise.bits' from 'bitwise/bits.py'>
>>> bitmask
<module 'bitwise.bitmask' from 'bitwise/bitmask.py'>
>>> bin(bits.AND(0b1010,0b1100))
'0b1000'
>>> bin(bits.OR(0b1010,0b1100))
'0b1110'
>>> bin(bits.NOT(0b1010))
'-0b1011'
>>> bin(bits.NOT(0b1010) & 0xFF)
'0b11110101'
>>> bin(bits.NOT(0b1010) & 0xF)
'0b101'
>>> bm = bitmask.BitMask(0b1100)
>>> bin(bm)
'0b1100'
>>> bin(bm.AND(0b1110))
'0b1100'
>>> bin(bm.OR(0b1110))
'0b1110'
>>> bm.listbits()
[1, 1, 0]

Vous avez créé une classe basée sur le type entier intégré de Python, int.
Comme vous ne donnez que de nouvelles méthodes pour la classe et que
vous ne stockez pas d’attributs de données supplémentaires, vous n’avez
pas besoin de donner un constructeur __new__() ou un initiateur __init__().
Les méthodes sont toutes vraiment similaires aux fonctions écrites dans
bits.py, mais vous avez créé une instance BitMask comme type retourné. La
méthode listbits() montre aussi une approche alternative consistant à dériver
la liste à l’aide de la représentation de chaîne bin() et à créer cette liste à
l’aide d’une conversion de caractères en nombres entiers à l’aide de int().
La fonction listbits() a aussi été étendue pour donner une paire de
paramètres de début et de fin qui, par défaut, correspondent à la longueur
complète du nombre binaire, mais peuvent aussi être utilisés afin d’extraire
un sous-ensemble de bit. Il y a un peu de travail extra selon que l’indice est
positif ou bien négatif. Notez que les index négatifs ne nécessitent pas
l’ajout de deux caractères parce qu’ils s’appliquent automatiquement à
partir de l’extrémité droite ; par conséquent, une affectation conditionnelle
Python garantit que la valeur finale correcte est définie.
Après avoir créé la classe, vous l’avez par la suite testée comme un
module standard en l’important depuis le local directory. Vous avez par la
suite répété une série de test similaires à ceux que vous avez effectués pour
bits.py. Une chose essentielle à prendre en compte : vous pouvez combiner
et assortir les opérateurs bit à bit traditionnels avec les nouvelles versions
fonctionnelles. Vous pouvez également comparer les objets BitMask
comme n’importe quel autre entier, comme vous l’avez vu dans l’exemple
shiftright(). Finalement, vous avez prouvé que votre nouvel algorithme
listbits() a fonctionné et que les nouveaux arguments supplémentaires
fonctionnent comme prévu pour les valeurs positives et négatives.
Ainsi, à ce stade, deux modules standard ont été créés dans un dossier.
Vous avez par la suite créé un file __init__.py vide qui a transformé le
dossier en paquet Python. Afin de vérifier que cela fonctionne, vous avez
changé directory pointant vers celui du haut pour que le paquet soit visible
par l’interpréteur. Une fois que vous avez confirmé que vous pouvez
importer le paquet et les modules qu’il contient, vous pouvez accéder à
quelques de ses fonctionnalités. Savoir la façon de créer et d’utiliser les
modules et paquets standard, ainsi que ceux que vous avez créés vous-
même, est un bon point de départ.
11

SCRIPT PYTHON

S ouvent, vous pouvez vous retrouver à effectuer des tâches qui


impliquent de nombreuses opérations répétitives. Afin de lutter contre
cette répétition du travail, il peut être possible d’écrire une macro afin
d’automatiser ces tâches dans une seule application. Toutefois si les tâches
s’étendent sur de nombreuses applications, les macro sont rarement
efficaces. Par exemple, si vous vous effectuez le backup et l'archivage
d’une grande application multimédia Web, vous devrez probablement gérer
le contenu produit par un ou quelques outils multimédias, le code d’un IDE
et probablement aussi certains file du database. Au lieu des macro, un outil
de programmation externe est essentiel pour guider chaque application, ou
utilitaire, pour qu’il réalise sa partie de l’ensemble. Python est en particulier
adapté à ce type de rôle d’orchestration.
Le terme "scripting" peut avoir quelques significations, il est alors
crucial de clarifier à l’avance sa signification quand vous le rencontrerez
dans ce chapitre. Il s’agit de coordonner les actions d’autres programmes ou
applications afin d’effectuer une tâche comme l’impression de file en série
ou l’automatisation d’un flux de travail, comme l’ajout d’un nouvel
utilisateur. Il peut s’agir d’outils du système d’exploitation ou bien de
grands paquets génériques comme une suite de productivité bureautique.
Pensez à la façon dont le scénario d’une pièce de théâtre indique aux
acteurs ce qu’ils doivent dire, où ils doivent se tenir et lorsqu’ils doivent
entrer ou sortir de scène. C’est ce que fait le "scripting" de Python: il
coordonne le comportement d’autres programmes.
Dans ce chapitre, vous apprendrez à utiliser les modules Python afin de
contrôler les paramètres de l’utilisateur ainsi que les niveaux d’accès aux
directory et aux file; définir l’environnement correct pour une opération; et
lancer et contrôler des programmes externes à partir de votre script. Vous
découvrirez aussi la façon que les modules Python vous aident à accéder
aux données dans les formats de file courants, la façon de gérer les dates et
les heures, et enfin, la façon d’accéder directement aux interfaces de
programmation de bas niveau des applications externes à l’aide du puissant
module ctypes et, pour Windows, du paquet pywin32. La majorité des
tâches qu’un programmeur typique doit effectuer à l’aide du système
d’exploitation, comme la collecte d’informations sur l’utilisateur ou la
navigation dans le file system, peuvent être faites de façon générique à
l’aide de la bibliothèque de modules standard de Python (souvenez-vous
que les modules sont des morceaux de code réutilisables qui peuvent être
partagés entre de nombreux programmes). Il est important de noter que les
modules clés ont été écrits de telle manière que les particularités du
comportement de chaque système d’exploitation ont été cachées derrière un
ensemble d’objets et d’opérations d’un niveau d’abstraction supérieur. Les
modules que nous allons considérer dans cette section sont os/path, pwd,
glob, shutil et subprocess. Le matériel illustré ici se concentre sur la façon
d’utiliser ces modules dans des scénarios courants. En effet, il ne prétend
pas couvrir l’ensemble des options ou scénarios possibles.
Le module os, comme son nom l’indique, donne accès à de nombreuses
fonctionnalités du système d’exploitation. Il s’agit en fait d’un paquetage
avec un sous-module, os.path, qui gère les chemins, les noms et les types de
file. Notez que le module os est soutenu par de plusieurs autres modules
que nous observerons dans ce chapitre. Ces nombreux modules sont appelés
collectivement le module OS (en majuscules) et le module du système
d’exploitation proprement dit, os (en minuscules). De plus, si vous êtes
familier avec la programmation de systèmes sur un système UNIX, ou
même avec l’utilisation d’un shell UNIX comme Bash, beaucoup d’entre
eux vous seront familiers. Le système d’exploitation sert principalement à
gérer l’accès au hardware du computer sous la forme de CPU, de la
mémoire, du stockage et du réseau, à réguler l’accès à ces ressources et à
gérer la création, l’ordonnancement et la suppression des processus. Les
fonctions du module OS donnent des informations et un contrôle sur ces
activités du système d’exploitation. Dans les prochaines sections, vous
analyserez ces activités courantes :

Récupération des informations sur l’utilisateur et le système


Gestion des processus
Récupérer des informations et manipuler file
Naviguer dans les dossiers

L’une des premières choses que nous pouvons faire lors de l’exploration
des modules de OS est de découvrir ce qu’ils peuvent nous apprendre sur
les utilisateurs. Vous pouvez notamment découvrir l’ID utilisateur, le nom
de connexion et quelques de ses paramètres par défaut. Comme la majorité
des nouvelles choses en Python, la meilleure manière de se familiariser
avec lui est par l’intermédiaire prompt interactive, alors démarrez
l’interpréteur Python et essayez-le.

1. Démarrez l’interpréteur Python


2. Entrez le code ci-dessous :

>>> import os
>>> os.getlogin()
'agauld'
>>> os.getuid() # Not Windows
1001
>>> import pwd # Not Windows
>>> pwd.getpwuid(os.getuid()) # Not Windows
pwd.struct_passwd(pw_name='agauld', pw_passwd='unused',
pw_uid=1001,
pw_gid=513, pw_gecos='Alan Gauld,U-DOCUMENTATION\\agauld,
S-1-5-21-2472883112-933775427-2136723719-1001',
pw_dir='/home/agauld', pw_shell='/bin/bash')
>>> for id in pwd.getpwall():
... print(id[0])
...
SYSTEM
LocalService
NetworkService
Administrators
TrustedInstaller
Administrator
agauld
Guest
HomeGroupUser$
????????
>>>

Après avoir importé le module os dans la première ligne, vous obtenez le


nom de connexion sous forme de chaîne. Cette fonction est en général plus
utile afin de créer des prompt ou des messages personnalisés à l’écran.
Malheureusement, pour les utilisateurs de Windows, c’est tout. En effet, le
reste du code ne convient qu’aux systèmes basés sur UNIX. Toutefois, tout
n’est pas perdu, car vous pouvez aussi trouver certaines de ces informations
à partir des variables d’environnement. Notez que si vous avez un système
basé sur UNIX, utilisez os.getuid() pour obtenir l’ID de l’utilisateur comme
le système d’exploitation le voit, cela signifie qu’une valeur numérique, que
vous pouvez ensuite utiliser avec diverses autres fonctions. Les prochaines
lignes importent et utilisent les fonctions du module de mot password, pwd,
pour traduire l’ID utilisateur du système d’exploitation en un ensemble plus
complet d’informations comprenant le nom réel, la shell par défaut et le
directory home. En effet, ceci est évidemment beaucoup plus informatif,
mais nécessite l’UID de os.getuid() comme point de départ. Une fonction
alternative, os.getpwnam(), prend le nom de login et renvoie les mêmes
informations. Finalement, en utilisant pwd.getpwall() et une boucle for,
vous pouvez extraire tous les noms d’utilisateurs de ce système.
Alors, vous pouvez savoir quel type de permissions les utilisateurs ont
sur les file qu’ils créent. Ceci est essentiel parce que cela affecte tous les
file produits par le code. Vous pouvez avoir besoin de changer
temporairement les autorisations, par exemple, si vous devez créer un file
qui sera exécuté plus tard dans le programme, le file doit avoir des
privilèges d’exécution. Sous UNIX, ces paramètres sont stockés dans ce
que nous nommons umask ou masque utilisateur. Il s’agit d’un masque de
bit, comme ceux utilisés dans les chapitres précédents, où chaque bit
représente un point de données d’accès utilisateur, comme décrit ci-dessous.
Python vous donne l’occasion de consulter la valeur de l’umask, même sous
Windows, à l’aide de la fonction os.umask(). Toutefois, la fonction
os.umask() présente une légère bizarrerie dans son utilisation. En effet, elle
s’attend à ce que vous passiez une nouvelle valeur à la fonction ; elle définit
ensuite cette valeur et renvoie l’ancienne valeur. Mais si vous souhaitez
simplement connaître la valeur actuelle, vous ne pouvez pas le faire. Plutôt
que cela, vous devez définir umask à une nouvelle valeur temporaire, lire
l’ancienne et par la suite restaurer la valeur à l’original. Le format du
masque est très compact, il se compose de 3 groupes de 3 bit, un groupe
pour chacune des permissions Propriétaire, Groupe et Monde,
respectivement. Au sein d’un groupe, les 3 bit représentent chacun un type
d’accès : lecture, écriture ou exécution, le plus fréquemment écrit en
utilisant une notation binaire explicite.

1. Lancez l’interpréteur Python


2. Entrez le code ci-dessous :

>>> import os
>>> os.umask(0b111111111) # binary for all false ‐ 111 x 3
18
>>> bin(18)
'0b10010'
>>> os.umask(18)
511
Vous avez débuté par appeler os.umask() avec une valeur binaire de
111111111. Cela définit toutes les permissions à false, ce que vous avez fait
comme une fonction de sécurité au cas où quelque chose se passe mal. Il est
mieux d’avoir un masque trop restrictif que de laisser l’utilisateur
vulnérable aux exploit de sécurité. Python a par la suite imprimé la valeur
décimale 18, l’appel de la fonction bin() montre que 18 a la valeur de
masque binaire de 10010. Notez que si vous le remplissez de zéros afin
d’obtenir le masque complet de 9 bit et le divisez en groupes de 3 bit, vous
observerez qu’il est de 000-010-010, cette valeur représente un accès
complet au Propriétaire, mais seulement un accès en lecture et en exécution
aux utilisateurs du Groupe et du Monde. Finalement, vous avez restauré le
paramètre utilisateur original en appelant os.umask() à nouveau avec un
argument de 18 (la valeur originale retournée par umask) et la valeur de
masque précédente (111111111) que vous aviez définie a été imprimée, en
décimal, comme 511.
Quelquefois, vous souhaitez savoir quel type de système informatique
l’utilisateur utilise, en particulier vous souhaitez connaître les détails du
système d’exploitation lui-même. Python a quelques manières de le faire,
mais celle que vous allez voir en premier est certainement la propriété
os.name. Au moment de la rédaction, cette propriété renvoie l’une des
valeurs suivantes: posix, nt, mac, os2, ce ou java. Un autre endroit où
rechercher le système utilisé par l’utilisateur est le module sys et, en
particulier, l’attribut sys.platform. Cet attribut renvoie souvent des
informations légèrement diverses de celles trouvées en utilisant os.name.
Par exemple, Windows est signalé comme win32 au lieu de nt ou ce. Sous
UNIX, une autre fonction de os appelée os.uname() donne un peu plus de
détails. Si vous disposez de quelques systèmes d’exploitation, il peut être
intéressant de comparer les résultats de ces diverses techniques. Nous
conseillons d’utiliser l’option os.name simplement, car elle est
universellement disponible et qu’elle renvoie un ensemble bien défini de
résultats. Une autre information qu’il est souvent utile de recueillir est la
taille du terminal de l’utilisateur sur le plan de lignes et de colonnes. Vous
pouvez utiliser ces informations pour changer l’affichage des messages de
vos script. Le module shutil donne une fonction pour cela appelée
shutil.get_terminal_size(), et elle est utilisée de cette manière :
>>> import shutil
>>> cols, lines =
shutil.get_terminal_size()
>>> cols
80
>>> lignes
49

Si la taille du terminal ne peut être déterminée, la valeur de retour par


défaut est 80 × 24. Il est possible de spécifier une valeur par défaut distincte
en tant qu’argument facultatif, mais 80 × 24 est en général une option
judicieuse parce qu’il s’agit de la taille traditionnelle des émulateurs de
terminal.
Informations sur les processus
Il peut être utile pour un programme de savoir quelque chose sur son état
actuel et son environnement de runtime. Par exemple, vous pouvez vouloir
connaître l’identité du processus ou savoir si le processus a un dossier
favori dans lequel il écrit ses file de données ou lit les données de
configuration. Les modules OS donnent des fonctions afin de déterminer
ces valeurs. L’une de ces sources d’informations sur le processus est
l’environnement du processus, comme défini par les variables
d’environnement. Le module os donne un dictionnaire nommé os.environ
qui contient l’ensemble des variables d’environnement pour le processus
actuel. L’inconvénient des variables d’environnement est qu’elles sont
vraiment volatiles ; les utilisateurs peuvent les créer et les supprimer. Les
applications peuvent faire de même, il est alors dangereux de se fier à
l’existence d’une variable d’environnement ; vous devez toujours avoir une
valeur par défaut sur laquelle vous pouvez vous rabattre. Heureusement,
quelques valeurs sont assez fiables et en général présentes. Trois d’entre
elles sont particulièrement utiles pour les utilisateurs de Windows parce que
les fonctions pwd.getpwuid() et os.uname() évoquées précédemment ne
sont pas disponibles. Il s’agit de HOME, OS et
PROCESSOR_ARCHITECTURE. Si vous tentez d’accéder à une variable
qui n’est pas définie, vous obtenez l’habituelle Python KeyError. Sur la
majorité des systèmes d’exploitation, mais pas tous, un programme peut
définir ou changer des variables d’environnement. Si cette fonctionnalité est
prise en charge par votre système d’exploitation, Python reflète l’ensemble
des modifications apportées au dictionnaire os.environ dans
l’environnement du système d’exploitation.
Outre l’utilisation des variables d’environnement comme source
d’informations sur l’utilisateur, il est assez fréquent de les utiliser afin de
définir des détails de configuration spécifiques à l’utilisateur sur un
programme, comme l’emplacement d’un database. Cette pratique est un peu
contestée de nos jours et il est considéré comme préférable d’utiliser un file
de configuration pour ces détails. Mais si vous deviez travailler avec des
applications plus anciennes, vous devriez probablement vous référer à
l’environnement pour ces détails.

1. Démarrez l’interpréteur Python


2. Entrez le code ci-dessous :

>>> import os
>>> os.getpid()
16432
>>> os.getppid()
3165
>>> os.getcwd()
/home/agauld
>>> len(os.environ)
48
>>> os.environ['HOME']
'/home/agauld'
>>> os.environ['testing123']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python2.7/UserDict.py", line 23, in __getitem__
raise KeyError(key)
KeyError: 'testing123'
>>> os.environ['testing123'] = 42
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python2.7/os.py", line 471, in __setitem__
putenv(key, item)
TypeError: str expected, not int
>>> os.environ['testing123'] = '42'
>>> os.environ['testing123']
'42'
>>> del(os.environ['testing123'])
>>> os.environ['testing123']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python2.7/UserDict.py", line 23, in __getitem__
raise KeyError(key)
KeyError: 'testing123'
Après avoir importé os dans la première ligne, la première chose à faire est
de déterminer l’ID du processus actuel. Vous êtes dans l’interpréteur
Python, c’est alors le processus propre à l’interpréteur que vous identifiez.
La prochaine ligne lit l’ID du processus parent ; dans ce cas, il s’agit du
programme shell du système d’exploitation. Vous pouvez utiliser ces ID
quand vous interagissez avec d’autres outils du système d’exploitation.
Vous avez ensuite utilisé la fonction os.getcwd() (qui signifie get current
working directory) afin de déterminer quel est la directory par défaut. Il
s’agit en général du directory à partir duquel l’interpréteur a été invoqué,
mais, comme vous le constaterez, il est possible de modifier directory dans
votre programme. La fonction os.getcwd() est un moyen utile de vérifier
l’emplacement exact de votre code à un moment donné. Par la suite, vous
avez découvert la taille de votre environnement en consultant la longueur
du dictionnaire os.environ, ensuite vous avez extrait la valeur de la variable
d’environnement HOME qui indique la home directory de l’utilisateur.
Vous avez tenté d’accéder à une variable nommée testing123, mais comme
elle n’existe pas, vous avez obtenu un KeyError. Vous avez tenté de créer
une variable testing123 en lui attribuant le nombre 42, mais cela a produit
un TypeError parce que les variables d’environnement doivent être des
chaînes de caractères. En assignant la valeur de la chaîne '42' à la variable
testing123, vous avez réussi à créer une nouvelle variable d’environnement
pour ce processus (souvenez-vous que l’environnement est local à ce
processus et à tous les sous-processus qu’il génère, il n’est pas visible pour
les autres processus externes). Vous lisez par la suite à nouveau la valeur et
obtenez cette fois une valeur sans message d’erreur, confirmant que tout
s’est bien passé. Finalement, vous avez supprimé la variable et prouvé
qu’elle avait disparu en tentant de la lire à nouveau, ce qui a entraîné,
comme prévu, une erreur.
Il est souvent utile de pouvoir exécuter d’autres programmes à partir
d’un script, et le module subprocess est l’outil privilégié pour cela. Le
module donne une classe appelée Popen qui donne une interface vraiment
puissante et flexible avec des programmes externes ; elle fournit aussi un
certain nombre de fonctions utiles que vous pouvez utiliser lorsque vous
préférez une approche plus facile. La documentation décrit comment
utiliser toutes ces fonctions ; dans cette section, seule la fonction la plus
simple, subprocess.call(), et la classe Popen sont utilisées. L’utilisation la
plus élémentaire du module consiste à appeler une commande externe du
système d’exploitation et à lui permettre simplement de suivre son cours.
Notez que l’output est en général affichée à l’écran ou stockée dans un file
de données quelque part. À la fin du programme, vous pouvez demander à
l’utilisateur de faire une sélection sur la base de ce qui a été affiché ou vous
pouvez accéder au file de données directement à partir de votre code. Il est
important de savoir qu’il est possible de forcer de plusieurs outils du
système d’exploitation, notamment sur les systèmes UNIX, à produire un
file de données en output en fournissant des options de ligne de commande
appropriées ou en utilisant la redirection de file du système d’exploitation.
Cette technique est une façon vraiment puissante d’exploiter la puissance
des outils du système d’exploitation de façon à ce que Python puisse les
utiliser pour un traitement ultérieur. Ce mécanisme de base afin d’appeler
un programme est encapsulé dans la fonction subprocess.call(). Le premier
paramètre de cette fonction est une liste de chaînes de caractères, suivie de
plusieurs paramètres facultatifs de mots-clés utilisés pour contrôler les
positions de input et de output et quelques autres éléments. La manière la
plus facile de voir comment cela fonctionne est de l’essayer.

1. Créez une directory de test nommé root et ajoutez certains file de


texte (créez-les de toutes pièces ou copiez-les depuis un autre
endroit). Peu importe ce qu’ils contiennent, seule leur présence
vous intéresse à ce stade. Afin d’obtenir les mêmes résultats qu’ici,
la structure devrait ressembler à ceci :

root
fileA.txt
fileB.txt

1. Naviguez vers le répertoire root et démarrez l’interpréteur Python.


2. Entrez le code ci-dessous :

>>> import subprocess as sub


>>> sub.call(['ls']) # Not Windows
fileA.txt fileB.txt
0
>>> sub.call(['ls'], stdout=open('ls.txt', 'w')) # Not windows
0
>>> sub.call(['cmd', '/c', 'dir', '/b']) # Windows only
fileA.txt
fileB.txt
0
>>> sub.call(['cmd', '/c', 'dir', '/b'], stdout=open('ls.txt','w')) # Windows
only
0
>>> sub.call(['more','ls.txt']) # Not Windows
fileA.txt
fileB.txt
ls.txt
0
>>> sub.call(['cmd','/c','type','ls.txt']) # Windows only
fileA.txt
fileB.txt
ls.txt
0
>>> for line in open('ls.txt'): print(line)
...
fileA.txt
fileB.txt
ls.txt

Après avoir importé subprocess avec l’alias sub dans la première ligne (cela
évite de taper un peu plus tard !), vous appelez sub.call() pour la première
fois, avec un argument ['ls'] ou ['cmd', '/c’, 'dir', '/b'] selon votre système
d’exploitation (remarquez que dir est en fait une sous-commande du
processus shell cmd.exe, le système d’aide de Windows explique ce que
font les flag /ce /b). L’output est démontrée sur stdout (votre terminal), mais
les noms de les file ne sont pas accessibles depuis Python. La seule chose
renvoyée par call() est le code de retour du système d’exploitation, qui vous
affiche si le programme s’est terminé avec succès ou non, mais ne vous aide
en aucune manière à interagir avec les données. Vous avez par la suite
utilisé sub.call() une deuxième fois, mais cette fois vous avez redirigé
stdout vers un nouveau file: ls.txt.
Par la suite, l’outil more du système d’exploitation (ou type sur
Windows) a été utilisé pour afficher le file. Le fait que ls.txt soit un file
texte normal veut aussi dire que vous pouvez accéder aux données en
ouvrant le file et en le traitant de la façon habituelle à l’aide de commandes
Python. Dans ce cas, vous avez effectué simplement un loop sur les lignes
et les avez imprimées, toutefois, vous auriez pu stocker et utiliser les
données à d’autres fins tout aussi facilement. Il convient de noter que
l’exposition d’une liste de file dans un file de texte comme celui-ci
constitue un problème de sécurité potentiel et que vous devez supprimer le
file dès que possible après l’avoir traité.
Un souci qui peut survenir pendant l’exécution de programmes externes
est que le système d’exploitation ne trouve pas la commande. Un message
d’erreur est en général indiqué dans ce cas et il est essentiel de donner
explicitement le chemin d’accès complet de le file du programme, en
supposant qu’il existe réellement. Finalement, réfléchissez à la manière
d’interrompre un processus en cours. En ce qui concerne les programmes
interactifs, le plus facile est que l’utilisateur ferme le programme externe
normalement ou émette un signal d’abandon en utilisant Ctrl+C ou Ctrl+Z,
ou toute autre norme du système d’exploitation de l’utilisateur. Cependant,
pour les programmes non interactifs, il peut être nécessaire d’intervenir à
partir du système d’exploitation, en général, en examinant la liste des
processus en cours et en mettant explicitement fin au processus défectueux.
Vous venez de constater combien il est facile d’utiliser subprocess.call()
afin de lancer un processus externe. Observons maintenant comment le
module sous-processus vous donne un contrôle beaucoup plus crucial sur
les processus et, en particulier, comment il permet à votre programme
d’interagir avec eux pendant leur exécution, notamment la façon de lire
l’output du processus directement à partir de votre script.

Encore plus de contrôle


Vous pouvez utiliser la classe Popen afin de créer une instance d’un
processus ou d’une commande. Malheureusement, la documentation peut
paraître plutôt décourageante parce que le constructeur de Popen possède
un grand nombre de paramètres. L’excellente nouvelle est que presque tous
ces paramètres possèdent des valeurs par défaut utiles et peuvent être
ignorés dans les cas les plus simples. Par conséquent, afin d’exécuter
simplement une commande du système d’exploitation à partir d’un script, il
suffit simplement de (les utilisateurs de Windows doivent remplacer la
commande dir de l’exemple précédent) :
>>> import subprocess as sub
>>> sub.Popen(['ls', '*.*'], shell=True)
<subprocess.Popen object at 0x7fd3edec>
>>> book tmp
Il est important de noter que l’argument shell=True, il est nécessaire
pour que la commande soit interprétée par le processeur de commande ou le
shell du système d’exploitation. Cela garantit que les caractères jolly ('*.*')
ainsi que les guillemets de chaîne et autres sont interprétés de la façon
prévue. Si le paramètre shell n’est pas utilisé, cela se produit :
>>> sub.Popen(['ls', '*.*'])
<subprocess.Popen object at 0x7fcd328c>
>>> ls: cannot access *.*: No such file or directory

Sans spécifier shell=True, le système d’exploitation essaie de trouver un file


avec le nom littéral '*.*', qui n’existe pas. Le souci de l’utilisation de
shell=True est qu’elle crée aussi des soucis de sécurité sous la forme d’une
attaque par injection potentielle ; par conséquent, ne l’utilisez jamais si vos
commandes sont formulées à partir de chaînes créées dynamiquement,
comme celles lues par un file ou bien par un utilisateur.
De plus, un injection attack se produit quand un utilisateur malveillant
tape une chaîne de input qui est lue et interprétée par le programme, mais
qui, plutôt que de contenir des données inoffensives, donne des commandes
potentiellement malveillantes pouvant entraîner la suppression de file, voire
pire. Le module shlex possède une fonction quote() pouvant atténuer les
risques, mais il faut tout de même faire attention lors de l’exécution de
chaînes de caractères générées dynamiquement. Afin d’accéder à l’output
de la commande en cours d’exécution, vous pouvez ajouter certaines
fonctionnalités extra à l’appel comme ceci :

>>> lsout = sub.Popen(['ls', '*.*'], shell=True, stdout=sub.PIPE).stdout


>>> for line in lsout:
... print (line)

Ici, vous spécifiez que stdout doit être un sub.PIPE, ensuite vous attribuez
l’attribut stdout de l’instance Popen à lsout (une pipe est simplement une
connexion de données à un autre processus, dans ce cas entre votre
programme et la commande que vous exécutez). Une fois ceci fait, vous
pouvez donc traiter la variable lsout comme un file Python normal, le lire et
ainsi de suite. Notez que vous pouvez envoyer des données au processus de
la même façon en spécifiant que stdin est une pipe sur lequel vous pouvez
par la suite écrire. Les valeurs valides que vous pouvez attribuer aux divers
flux comprennent file ouverts, les descripteurs de file ou d’autres flux (afin
que stderr puisse être affiché sur stdout, par exemple). Notez qu’il est
possible de concaténer des commandes externes en définissant, par
exemple, l’entrée du second programme comme la sortie du premier.
Remarquez que cela produit un effet similaire à l’utilisation du caractère
pipe (|) du système d’exploitation sur une ligne de commande.

1. Démarrez l’interpréteur Python dans le répertoire root


2. Entrez le code :

>>> import subprocess as sub


>>> sub.Popen(['ls']) # Windows use: ("cmd /c dir /b")
<subprocess.Popen object at 0x7fd3eecc>
fileA.txt fileB.txt ls.txt
>>> # Windows use: ("cmd /c dir /b", stdout=sub.PIPE)
>>> ls = sub.Popen(['ls'], stdout=sub.PIPE)
>>> for f in ls.stdout: print(f)
...
b'fileA.txt\n'
b'fileB.txt\n'
b'ls.txt\n'
>>> ex = sub.Popen(['ex', 'test.txt'], stdin=sub.PIPE) # Not Windows
>>> ex.stdin.write(b'i\nthis is a test\n.\n') # Not Windows
19
>>> ex.stdin.write(b'wq\n') # Not Windows
3
>>>
1+ Stopped python3
>>> sub.Popen(['NonExistentFile'])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.2/subprocess.py", line 745, in __init__
restore_signals, start_new_session)
File "/usr/lib/python3.2/subprocess.py", line 1361, in _execute_child
raise child_exception_type(errno_num, err_msg)
OSError: [Errno 2] No such file or directory: 'NonExistentFile'

Pour débuter, vous avez importé le subprocess avec l’alias sub. La première
paire de commandes reproduit tout simplement ce que vous avez exécuté
avec subprocess.call() en produisant initialement une liste de file sur stdout,
mais sans pouvoir utiliser ces données. Le deuxième cas est plus intéressant
parce que vous avez redirigé stdout vers un sub.PIPE qui vous a permis de
le lire via l’attribut stdout. Remarquez la différence entre le paramètre
stdout, que vous définissez à sub.PIPE dans l’appel du constructeur de
Popen, et l’attribut stdout que vous utilisez afin de lire les données de
l’instance Popen et qui est accessible via la dot notation.
Afin de l’utiliser, vous avez aussi affecté le résultat de l’appel Popen à
une variable appelée ls. Il est important de savoir que Popen est en fait une
classe et le résultat de l’appel est une nouvelle instance de l’objet Popen. Le
résultat final est vraiment similaire au cas du subprocess.call() où vous avez
envoyé l’output vers un file, ls.txt, et lu le file, mais dans ce cas vous ne
créez aucun file. Au contraire, vous lisez directement à partir du processus,
ce qui veut dire que vous ne finissez pas par encombrer votre file system
avec de petits file temporaires qui doivent être réorganisés ultérieurement.
Le prochain exemple utilise l’editor de ligne UNIX ex, mais cette fois vous
redirigez stdin vers sub.PIPE et entrez par la suite un certain nombre de
commandes dans l’editor pour créer un court file texte. Vous avez aussi
passé un nom de file comme argument en donnant à Popen une deuxième
chaîne de caractères dans le premier argument. Remarquez que l’input dans
stdin doit être une chaîne de byte (b'xxxx') au lieu d’une chaîne de texte
normale.
L’avant-dernier exemple illustre ce qui se passe si vous tentez d’ouvrir
un file inexistant (ou si vous ne fournissez pas les informations de chemin
correctes). Vous obtenez une exception OSError que vous pouvez, bien sûr,
attraper en utilisant la fonction try/except de Python.
Dans les exemples, vous avez accédé directement à stdin et stdout ;
toutefois, cela peut parfois poser des problèmes, notamment lors de
l’exécution simultanée de processus ou au sein de thread, ce qui entraîne le
remplissage de les pipe et le blocage des processus. Afin d’éviter ces
soucis, nous conseillons d’utiliser la méthode Popen.communicate() et
d’indexer le flux approprié. Ce moyen est légèrement plus difficile à
utiliser, mais elle permet d’éviter les soucis discutés ci-dessus. La fonction
Popen.communicate() prend une chaîne de input (équivalente à stdin) et
renvoie un tuple dont le premier élément est le contenu de stdout et le
second le contenu de stderr. Alors, la répétition de l’exemple de la liste de
les file en utilisant Popen.communicate() ressemble à ceci:

>>> ls = sub.Popen(['ls'], stdout=sub.PIPE)


>>> lsout = ls.communicate()[0]
>>> print (lsout)
b'fileA.txt\nfileB.txt\nls.txt\n'
>>>

Afin de conclure cette section, il convient de remarquer que, par souci de


simplicité, des commandes assez basiques, comme ls, ont été utilisées dans
les exemples. La majorité de ces commandes peuvent être exécutées de
façon équivalente à partir de Python lui-même. La véritable valeur des
mécanismes comme subprocess.call() et Popen() réside dans l’exécution de
programmes beaucoup plus difficiles comme les outils de conversion de file
et les outils batch de traitement d’images. Notez que l’écriture de la
fonctionnalité équivalente de ces outils en Python serait un projet majeur.
Par conséquent, l’appel du programme externe est une alternative beaucoup
plus raisonnable. Effectivement, vous devriez utiliser Python dans les
scénarios où il est le mieux adapté, pour orchestrer et valider les input et les
output, mais laisser le sale boulot aux applications dédiées.
Informations provenant de file
Le module os est fortement influencé par la manière dont les choses sont
faites sous UNIX. En tant que tel, il traite les appareils et les file de façon
similaire. La découverte des appareils ressemble alors beaucoup à la
découverte des file. Dans cette section, vous allez voir la manière de
déterminer l’état et les autorisations des file et même comment changer
certaines de leurs propriétés à partir de vos programmes. Considérons le
code ci-dessous :
>>> import os
>>> os.listdir('.')
['fileA.txt', 'fileB.txt', 'ls.txt', 'test.txt']
>>> os.stat('fileA.txt')
posix.stat_result(st_mode=33204, st_ino=1125899907117103,
st_dev=1491519654, st_nlink=1, st_uid=1001, st_gid=513,
st_size=257, st_atime=1388676837, st_mtime=1388677418,
st_ctime=1388677418)

Ici vous avez vérifié la liste du directory courant ('.') avec os.listdir(),
maintenant que vous avez vu os.listdir(), j’espère que vous réalisez que
votre utilisation de ls ou dir dans subprocess était plutôt artificielle, car
os.listdir() fait le même travail directement depuis Python, et le fait plus
efficacement. Vous avez par la suite utilisé la fonction os.stat() afin
d’obtenir des informations sur l’un des file, cette fonction renvoie un objet
tuple nommé contenant 10 éléments d’intérêt. Il est important de se
souvenir que les plus utiles d’entre elles sont probablement st_uid, st_size
et st_mtime. Ces valeurs représentent l’ID utilisateur du propriétaire du file,
la taille et la date/heure de la dernière modification. Notez que les temps
sont des entiers qui doivent être décodés à l’aide du module time, comme
ceci:
>>> import time
>>> time.localtime(1388677418)
time.struct_time(tm_year=2014, tm_mon=1, tm_mday=2, tm_hour=15,
tm_min=43, tm_sec=38, tm_wday=3, tm_yday=2, tm_isdst=0)
>>> time.strftime("%Y‐%m‐%d", time.localtime(1388677418))
'2014-01-02'

Dans cet exemple, vous avez utilisé la fonction localtime() du module time
afin de convertir la valeur entière st_mtime en un tuple indiquant les valeurs
de l’heure locale et, à partir de là, en une chaîne de date lisible à l’aide de la
fonction time.strftime() avec une chaîne de format appropriée. Le tuple de
10 valeurs retourné par os.stat() est en général utile, mais plus de détails
sont disponibles via os.stat(). Il est important de savoir que certaines de ces
valeurs supplémentaires dépendent du système d’exploitation, comme
l’attribut st_obtype que l’on trouve sur les systèmes RiscOS. Vous devez
faire un peu plus de travail afin de les extraire, mais vous pouvez accéder
aux détails en utilisant la notation par points sur l’attribut de l’objet. Notez
que le champ le plus intéressant auquel vous pouvez accéder à partir de
os.stat() est sans doute la valeur st_mode, qui vous indique les autorisations
d’accès au file. Vous l’utilisez de la manière suivante :
>>> import os
>>> stats = os.stat('fileA.txt')
>>> stats.st_mode
33204

Toutefois il n’est pas très utile ; il s’agit tout simplement d’un nombre
apparemment aléatoire ! Le secret réside dans les bit individuels qui
composent le nombre. En effet, il s’agit d’un autre masque de bit. Vous
vous rappelez probablement du masque de bit umask que vous avez vu
précédemment. Le st_mode est conceptuellement similaire à umask, mais
avec la signification des bit inversée. Vous pouvez constater comment les
détails de l’accès sont encodés en observant les 9 derniers bit, comme ceci :
>>> bin(stats.st_mode)[‐9:]
'111111101'

Il est important de savoir qu’en utilisant la fonction bin() en combinaison


avec slice, vous avez extrait la représentation binaire des 9 derniers bit. En
les considérant comme 3 groupes de 3, vous pouvez observer les valeurs de
lecture/écriture/exécution pour Propriétaire, Groupe et Monde
respectivement. Par conséquent, dans cet exemple, Owner et Group ont tous
les trois bit à 1 (True), mais World n’a que les bit de lecture et d’exécution à
1 (True) et l’accès en écriture est à 0 (False). Remarquez que ce sont les
inverses directs des significations des bit umask ; ne confondez pas les deux
! Les bit d’ordre supérieur ont aussi une signification et le module stat
possède un certain nombre de masques de bit qui peuvent être utilisés pour
extraire des détails sur une base bit par bit. Dans la majorité des cas, les bit
d’accès précédents sont suffisants, et il se trouve des fonctions de support
dans le module os.path afin d’accéder à ces informations.
Vous disposez aussi de plusieurs autres moyens afin de déterminer les
droits d’accès à un file en Python. En particulier, le module os donne une
fonction utile — os.access() — qui accepte un nom de file et une variable
flag (une parmi os.F_OK, os.R_OK, os.W_OK ou os.X_OK) qui renvoie un
résultat booléen selon que le file existe ou est respectivement lisible,
inscriptible ou exécutable. Ces fonctions sont toutes plus simples à utiliser
que l’approche sous-jacente os.stat() et bitmask, mais il est utile de savoir
d’où les fonctions obtiennent leurs données. Finalement, la documentation
du système d’exploitation signale un souci potentiel pendant la vérification
de l’accès avant l’ouverture de un file.
Il y a une très courte période entre les deux opérations lors de laquelle le
file peut changer de niveau d’accès ou de contenu. Par conséquent, comme
d’habitude en Python, il est mieux d’utiliser try/except pour ouvrir le file et
gérer l’erreur si elle se produit. Vous pouvez par la suite utiliser les
contrôles d’accès afin de déterminer la cause de l’erreur, si nécessaire. Le
schéma conseillé est illustré ci-dessous :
try:
myfile = open('myfile.txt')
except PermissionError:
# test/modify the permissions here
else:
# process the file here
finally:
# close the file here
Après avoir vu la façon d’explorer les propriétés des file individuels,
nous analysons maintenant les mécanismes disponibles pour le file system,
la lecture des dossiers, la copie, le déplacement et la suppression des file et
ainsi de suite. Python donne des fonctions intégrées pour ouvrir, lire et
écrire des file individuels. Le module os ajoute des fonctions permettant de
manipuler les file comme entités complètes, par exemple en les renommant,
en les supprimant et en créant des liens. Toutefois, le module os lui-même
ne fournit que la moitié des fonctions utiles lorsqu’il s’agit de travailler
avec les file. L’autre moitié est proposée par le module shutil et d’autres
modules utilitaires qui travaillent de concert avec os.
De plus, dans Python 3.4, un nouveau module nommé pathlib a été
introduit, qui vise à fournir une vue orientée objet du file system. Toutefois,
pathlib est marqué comme temporaire, ce qui signifie que les interfaces
pourraient modifier de façon significative dans les futures versions ou que
le module pourrait même être retiré de la bibliothèque. En effet, en raison
de cette incertitude, pathlib n’est pas utilisé dans cette section. Cela débute
par la lecture et la navigation dans le file system. Vous avez déjà vu la
manière que vous pouvez utiliser os.listdir() afin d’obtenir une liste de
directory et os.getcwd() afin d’obtenir le nom du directory de travail actuel.
Vous pouvez utiliser os.mkdir() afin de créer une nouveau directory et
os.chdir() afin de naviguer vers une autre directory.

1. Parcourez et entrez dans le dossier root.


2. Lancez l’interpréteur et exécutez les commandes:

>>> import os
>>> cwd = os.getcwd()
>>> print (cwd)
/home/agauld/book/root
>>> os.listdir(cwd)
['fileA.txt', 'fileB.txt', 'ls.txt']
>>> os.mkdir('subdir')
>>> os.listdir(cwd)
['fileA.txt', 'fileB.txt', 'ls.txt', 'subdir']
>>> os.chdir('subdir')
>>> os.getcwd()
'/home/agauld/book/root/subdir'
>>> os.chdir('..')
>>> os.getcwd()
'/home/agauld/book/root'
Après avoir importé os dans la première ligne, vous avez stocké la directory
courant dans cwd et l’avez par la suite imprimé afin de confirmer que vous
étiez là où vous pensiez être. Vous avez par la suite énuméré son contenu.
Ensuite, vous avez créé un répertoire nommé subdir et vérifié que le
déplacement a réussi en appelant os.getcwd() à nouveau depuis le nouveau
répertoire. Finalement, vous êtes retourné au directory précédent en utilisant
le raccourci '..' et avez vérifié une fois de plus que cela fonctionnait avec
os.getcwd(). Un souci avec la fonction os.mkdir() utilisée ici est qu’elle ne
peut créer une directory que dans une directory existant. Si vous tentez de
créer une directory dans un emplacement qui n’existe pas, cela échoue.
Python donne une fonction alternative nommée os.makedirs()—faites
attention à la différence d’orthographe—qui crée tous les répertoires
intermédiaires dans un chemin s’ils n’existent pas déjà. Vous pouvez
constater comment cela fonctionne avec les commandes ci-dessous:

>>> os.mkdir('test2/newtestdir')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OSError: [Errno 2] No such file or directory: 'test2/newtestdir'
>>> os.makedirs('test2/newtestdir')
>>> os.chdir('test2/newtestdir')
>>> print( os.getcwd() )
/home/agauld/book/root/test2/newtestdir

Ici, l’appel original os.mkdir() a produit une erreur parce que le dossier
intermédiaire test2 n’existait pas. L’appel à os.makedirs() a toutefois réussi
à créer à la fois les dossiers test2 et newtestdir et vous avez pu passer à
newtestdir afin de prouver le point. Remarquez que os.makedirs() génère
une erreur si le dossier cible existe déjà. Vous pouvez utiliser certains
paramètres supplémentaires afin d’optimiser davantage le comportement,
mais les valeurs par défaut sont en général ce dont vous avez besoin. Un
autre module, shutil, donne un certain nombre de commandes de
manipulation de les file de plus haut niveau. De plus, il est notamment
possible de copier file individuels, de copier des arborescences de directory
entières, de supprimer des arborescences de directory et de déplacer file ou
des arborescences de directory entières. Une anomalie est la possibilité de
supprimer un seul file ou un groupe de file. Ceci se situe en fait dans le
module os sous la forme de la fonction os.remove() pour les file (et
os.rmdir() pour les directory vides, bien que shutil.rmtree() soit plus
puissant et généralement ce que vous souhaitez). Il est important de savoir
qu’il y a un autre module utile nommé glob. Ce module donne l’occasion de
gérer les caractères jolly de le nom file. Vous connaissez peut-être les
caractères jolly ? et * utilisés afin de spécifier des groupes de file dans les
commandes du système d’exploitation. Par exemple, *.exe spécifie
l’ensemble de les file se terminant par .exe. La fonction glob.glob() fait la
même chose dans votre code en retournant une liste de noms de file
correspondants pour un motif donné.
Utilisez les fonctions os, glob et shutil afin de manipuler file entiers.
Suivez les étapes ci-dessous :

1. Naviguez vers le répertoire root


2. Créez un file portant le nom test.py, quel que soit son contenu.
3. Démarrez l’interpréteur et tapez :

>>> import os,glob,shutil as sh


>>> os.listdir('.') # everything in the folder
['fileA.txt', 'fileB.txt', 'ls.txt', 'subdir', 'test.py', 'test2']
>>> glob.glob('*') # everything in the folder
['fileA.txt', 'fileB.txt', 'ls.txt', 'subdir', 'test.py', 'test2']
>>> glob.glob('*.*') # files with an extension
['fileA.txt', 'fileB.txt', 'ls.txt', 'test.py']
>>> glob.glob('*.txt') # text files only
['fileA.txt', 'fileB.txt', 'ls.txt']
>>> glob.glob('file?.txt') # text files starting with 'file'
['fileA.txt', 'fileB.txt']
>>> glob.glob('*.??') # any file with a 2 letter extension
['test.py']
1. Examinez attentivement les différents set de résultats afin
d’observer l’effet des diverses combinaisons fonction/sujet.
2. Entrez le code ci-dessous :

>>> sh.copy('fileA.txt','fileX.txt')
>>> sh.copy('fileB.txt','subdir/fileY.txt')
>>> os.listdir('.')
['fileA.txt', 'fileB.txt', 'fileX.txt', 'ls.txt', 'subdir', 'test.py', 'test2']
>>> os.listdir('subdir')
['fileY.txt']
>>> sh.copytree('subdir', 'test3')
>>> os.listdir('.')
['fileA.txt', 'fileB.txt', 'fileX.txt', 'ls.txt', 'subdir', 'test.py',
'test2', 'test3']
>>> os.listdir('test3')
['fileY.txt']
>>> sh.rmtree('test2')
>>> os.listdir('.')
['fileA.txt', 'fileB.txt', 'fileX.txt', 'ls.txt', 'subdir', 'test.py', 'test3']

1. Examinez l’output des commandes que vous venez de taper avant


de taper le code ci-dessous:

>>> os.mkdir('test4')
>>> sh.move('subdir/fileY.txt', 'test4')
>>> os.listdir('test4')
['fileY.txt']
>>> os.listdir('subdir')
[]
>>> os.remove('test4/fileY.txt')
>>> os.listdir('test4')
[]
>>> os.remove('test4')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OSError: [Errno 1] Operation not permitted: 'test4'
>>> sh.rmtree('test4')
>>> sh.rmtree('test3')
>>> os.remove('fileX.txt')
>>> os.listdir('.')
['fileA.txt', 'fileB.txt', 'ls.txt', 'subdir', 'test.py']

Souvenez-vous que dans la première série de commandes, vous avez


comparé l’effet de os.listdir() avec différents motifs de la fonction
glob.glob(). En effet, le premier motif ('*') répliquait ce que os.listdir()
faisait en listant le contenu entier du répertoire. Le deuxième motif ('*.*')
listait tous les file avec une extension. À proprement parler, glob() ne sait
rien des file ou des dossiers. Effectivement, il travaille strictement avec les
noms, et liste alors tous les noms qui comportent un point, quel que soit le
type d’objet. Par la suite, vous avez utilisé '*.txt' afin de trouver tous les file
de texte, suivi de 'file?.txt' pour trouver tout file “txt” dont le nom
commence par file suivi d’un seul caractère. Finalement, vous avez utilisé
une combinaison de caractères jolly afin de trouver tout file dont le nom se
termine par deux caractères ('*.??'). La deuxième série de commandes
analyse les commandes de manipulation de les file de shutil. Vous avez
débuté par utiliser shutil.copy() afin de copier file individuels: fileA.txt vers
un nouveau file dans le même dossier nommé fileX.txt et fileB.txt vers le
sous-directory subdir avec un nouveau nom fileY.txt. Vous avez par la suite
utilisé os.listdir() deux fois afin d’afficher les résultats dans chaque dossier.
Vous avez par la suite analysé les opérations au niveau du directory avec la
fonction shutil.copytree(), qui a copié la directory subdir et son contenu
dans un nouveau dossier, test3, en le créant au passage. Encore une fois,
vous avez utilisé os.listdir() deux fois afin de confirmer les résultats. Vous
avez utilisé shutil.rmtree() afin de supprimer le répertoire test2 et son
contenu.
Vous avez débuté la séquence suivante en créant un autre nouveau
dossier, test4, en utilisant os.mkdir(). Dans ce nouveau dossier, vous avez
déplacé le file fileY.txt en utilisant sh.move() et là encore, l’utilisation de
os.listdir() sur les deux dossiers démontre que l’opération a réussi et que le
file n’existe plus dans subdir, mais existe dans test4. Finalement, vous avez
analysé la suppression des file avec os.remove(). Le premier exemple a
supprimé le file de test4 et a vérifié qu’il avait été supprimé. La prochaine
ligne essaie de supprimer test4 lui-même, mais produit une erreur, car
os.remove() ne fonctionne que sur file. Si vous devez supprimer une
directory, vous devez utiliser à nouveau shutil.rmtree() (ou vous auriez pu
utiliser os.rmdir() dans le même but). Finalement, en utilisant os.listdir()
une fois de plus, vous avez confirmé que le répertoire a disparu.
CONCLUSION

Apprendre à programmer est difficile. Ne croyez pas ceux qui vous disent
que c’est facile. La question essentielle à se poser est « Voulez-vous
apprendre Python ? ». Ce livre vous a certainement aidé à répondre à cette
question, mais préparez-vous à lutter, à commettre des erreurs, à être frustré
et à vous taper la tête contre le clavier.
C’est maintenant à votre tour d’approfondir d’autres aspects de Python,
de découvrir des bibliothèques intéressantes, de créer des applications et
bien plus encore. Si vous aimez résoudre des soucis complexes, si vous
aimez les Sudoku, les mots croisés ou les puzzle, en d’autres mots, si vous
aimez les défis, donc vous pourriez aimer programmer en Python. Ce n’est
pas simple, mais c’est assez facile pour commencer ; vous pouvez utiliser
ce e-book chaque fois que vous souhaitez revoir des fonctions ou clarifier
des doutes.
Soyez patient avec vous-même et faites semblant d’être le professeur et
l’élève en même temps. Quand l’élève échoue, l’excellent enseignant ne
l’insulte pas, ne le réprimande pas, mais l’encourage. L’excellent enseignant
sait que l’échec fait partie de l’apprentissage et que le fait de le surmonter
est gratifiant et permet d’apprendre davantage. Alors, soyez un excellent
professeur.
Mais tentez aussi d’être un excellent étudiant. Quand vous relirez ce
livre, vous devrez être assis à côté de votre computer. Fixez-vous un
objectif, par exemple, prévoyez de passer au moins deux heures sans
interruption ou bien prévoyez de résoudre une task simple au début. Ce
processus vous fera passer par l’apprentissage de base et à partir de là, ce
sera une transition continue du livre à la visualisation, l’édition et l’écriture
du code. Nous n’apprenons pas la programmation en lisant, mais en
s’exerçant.
Quand vous avez débuté à lire ce e-book, vous n’aviez probablement
aucune connaissance préalable de Python, mais maintenant que vous
connaissez les bases, vous serez en mesure d’aborder des sujets assez
avancés comme le travail avec la programmation orientée objet, les
database ou bien l’utilisation de Python pour le Web.
Maintenant, tout est entre vos mains, vous avez une base, faites-en bon
usage.

Vous aimerez peut-être aussi