Rick Muller, Sandia National Laboratories

traduit par Raphaël Seban (tarball69) pour Developpez.com

Utiliser Python comme une calculatrice

Avant, j'utilisais une calculatrice ; mais ça, c'était avant :

In [1]:
2+2
Out[1]:
4
In [2]:
(50-5*6)/4
Out[2]:
5

Note : si vous entrez ces exemples dans un document IPython Notebook, pensez à presser [Shift] [Enter] au clavier pour exécuter le code Python contenu dans la cellule de code et obtenir ainsi l'affichage du résultat.

Il y a parfois quelques mauvaises surprises, à comparer avec une calculette.

In [3]:
7/3
Out[3]:
2

La division entière Python – tout comme en C ou en Fortran – enlève le reste de la division et ne retourne qu'un nombre entier. Ceci est vrai en Python 2, mais pas en Python 3, qui lui retourne un nombre décimal à virgule flottante. Vous pouvez obtenir un aperçu du comportement Python 3 en Python 2 en important le module spécial « future » :

from __future__ import division

In [4]:
7/3.
Out[4]:
2.3333333333333335
In [5]:
7/float(3)
Out[5]:
2.3333333333333335

Dans les dernières lignes, nous sommes allés un peu vite et nous devrions peut-être nous y attarder quelque peu. Nous avons vu – bien que brièvement – deux nouveaux types de données : les entiers (int) et les nombres à virgule flottante (float), connus parfois – bien que ce soit incorrect – sous l'appellation nombres décimaux.

Nous avons aussi vu, pour la première fois, l'utilisation du mot-clé Python import. Python a un nombre considérable de librairies d'extension fournies d'office lors de l'installation du langage. Pour faire simple, la plupart des fonctionnalités qui ne sont pas fondamentales au langage ne sont pas accessibles directement. Si vous voulez une ou plusieurs fonctionnalités données, vous devez importer dans la mémoire de l'interpréteur le module contenant ces fonctionnalités en citant son nom. Cela permet de ne pas encombrer inutilement le système : on ne prend que ce dont on a besoin. Par exemple, si vous voulez faire des opérations mathématiques un peu plus élaborées que la simple arithmétique, vous disposez du module math qui contient de nombreuses fonctions et constantes très utiles. Pour utiliser la fonction racine carrée (square root en anglais – sqrt(x)), vous devez tout d'abord réclamer :

from math import sqrt

et ensuite seulement saisir :

In [6]:
sqrt(81)
Out[6]:
9.0

ou, si vous devez utiliser plusieurs fonctions, préférez appeler le module math en entier :

In [7]:
import math
math.sqrt(81)
Out[7]:
9.0

Pour définir une variable, utilisez le signe égal (=) :

In [8]:
width = 20
length = 30
area = length * width
area
Out[8]:
600

Si vous faites référence à une variable qui n'a pas encore été définie, vous obtiendrez un message d'erreur :

In [9]:
volume
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-9-0c7fc58f9268> in <module>()
----> 1 volume

NameError: name 'volume' is not defined

Il faut donc toujours définir une variable avant de s'y référer :

In [10]:
depth = 10
volume = area*depth
volume
Out[10]:
6000

On peut nommer une variable presque comme bon nous semble. Le nom de la variable doit toutefois débuter par une lettre de l'alphabet ou le signe souligné '_' (underscore) ; les chiffres ne sont pas admis en début de nom de variable.

*NdT : la PEP 8 de Python recommande malgré tout de suivre des règles précises afin de faciliter la relecture et l'interchangeabilité du code entre les développeurs. En gros, un nom de variable ne devrait contenir que des lettres minuscules non accentuées allant de 'a' à 'z' (alphabet anglais), le signe souligné '_' (underscore) et des chiffres allant de '0' à '9'.*

Certains noms sont en réalité des mots-clés réservés par le langage et ne doivent pas être utilisés pour nommer des variables :

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

Si vous tentez de définir un mot-clé réservé comme une variable, vous aurez un message d'erreur :

In [11]:
return = 0
  File "<ipython-input-11-2b99136d4ec6>", line 1
    return = 0
           ^
SyntaxError: invalid syntax

Vous trouverez plus d'information dans le tutoriel Python ainsi que dans le tutoriel IPython qui vient en complément.

Chaînes de caractères

Les chaînes de caractères (str) sont des suites de signes imprimables qui peuvent être définies soit à l'aide de guillemets simples (') placés de part et d'autre de la chaîne :

In [12]:
'Hello, World!'
Out[12]:
'Hello, World!'

soit à l'aide de guillemets anglais (") :

In [13]:
"Hello, World!"
Out[13]:
'Hello, World!'

Mais pas les deux en même temps, à moins que vous ne vouliez incorporer les uns dans les autres comme signes simplement imprimables (non déclaratifs).

In [14]:
"He's a Rebel"
Out[14]:
"He's a Rebel"
In [15]:
'She asked, "How are you today?"'
Out[15]:
'She asked, "How are you today?"'

Tout comme les deux types de données que nous avons vu précédemment (entiers int et nombres à virgule flottante float), il est possible d'assigner une chaîne de caractères (str) à une variable :

In [16]:
greeting = "Hello, World!"

Le mot-clé réservé print permet d'afficher des chaînes de caractères dans la console :

In [17]:
print greeting
Hello, World!

Plus globalement, ce mot-clé permet d'afficher toute sorte de résultats :

In [18]:
area = 600
print "The area is ", area
The area is  600

Dans l'exemple ci-dessus, le nombre 600 stocké dans la variable area est automatiquement converti en chaîne de caractères par print avant d'être affiché comme résultat dans la console.

Vous pouvez utiliser l'opérateur plus (+) pour concaténer (enchaîner) plusieurs chaînes de caractères entre elles :

In [19]:
statement = "Hello," + "World!"
print statement
Hello,World!

Lors d'une concaténation, pensez à rajouter les espaces blanches qui pourraient manquer à l'intérieur même des déclarations de chaînes de caractères :

In [20]:
statement = "Hello, " + "World!"
print statement
Hello, World!

Vous pouvez bien sûr utiliser l'opérateur plus (+) plusieurs fois dans une même expression :

In [21]:
print "This " + "is " + "a " + "longer " + "statement."
This is a longer statement.

Il existe d'autres moyens plus efficaces de concaténer de nombreux mots entre eux, mais l'opérateur plus (+) demeure très pratique pour assembler quelques chaînes de caractères entre elles, vite fait.

Listes

En programmation, il arrive fréquemment de vouloir regrouper des éléments similaires au sein d'une même structure. Python fournit une structure de ce type : la liste (type list ou []).

In [22]:
days_of_the_week = ["Sunday","Monday",
                    "Tuesday","Wednesday","Thursday","Friday","Saturday"]

On accède aux éléments d'une liste par leur numéro d'index (position de l'élément dans la liste) :

In [23]:
days_of_the_week[2]
Out[23]:
'Tuesday'

Le comptage des indices en Python – comme en C mais pas comme en Fortran – débute à zéro (0) pour le premier élément, 1 pour le second, 2 pour le troisième, etc. Dans notre exemple, l'élément d'index 0 est "Sunday", l'élément d'index 1 est "Monday" et ainsi de suite. Si vous voulez accéder au énième élément à partir de la fin de la liste, vous pouvez utiliser des indices négatifs. Par exemple, l'élément d'index -1 sera toujours le dernier élément de la liste, quelle que soit sa longueur, l'élément d'index -2 sera l'avant-dernier, etc. :

In [24]:
days_of_the_week[-1]
Out[24]:
'Saturday'

On peut agrandir une liste référencée par une variable grâce à la méthode append() :

In [25]:
languages = ["Fortran","C","C++"]
languages.append("Python")
print languages
['Fortran', 'C', 'C++', 'Python']

La fonction range() permet de générer des listes séquentielles de nombres :

In [26]:
range(10)
Out[26]:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Notez que range(n) génère une liste de valeurs entières allant de 0 à n-1 (n exclus). Si vous voulez démarrer votre liste à une autre valeur, utilisez plutôt range(start, stop) :

In [27]:
range(2,8)
Out[27]:
[2, 3, 4, 5, 6, 7]

La liste créée ci-dessus va de 2 (inclus) à 8 (exclus), donc de 2 à 7 avec un pas incrémental entier de 1 entre chaque élément. Vous pouvez également choisir le pas incrémental de votre liste grâce à range(start, stop, step) :

In [28]:
evens = range(0,20,2)
evens
Out[28]:
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
In [29]:
evens[3]
Out[29]:
6

Les listes Python autorisent le mix de plusieurs types de données parmi les éléments. Vous pouvez tout à fait écrire, par exemple :

In [30]:
["Today",7,99.3,""]
Out[30]:
['Today', 7, 99.3, '']

Toutefois, il est recommandé – bien que pas du tout obligatoire – d'utiliser les listes pour regrouper des éléments de même type entre eux au sein d'une même structure. Si vous voulez regrouper des éléments de types hétérogènes au sein d'une structure, préférez utiliser les tuples – que nous aborderons un peu plus tard.

NdT : cette recommandation de l'auteur est plus que discutable : les tuples sont conçus pour être des structures fixes (immutables) et les listes des structures évolutives (mutables) – rien à voir donc, avec le fait que leurs éléments soient composites ou non.

Utilisez la fonction len(liste) pour obtenir le nombre d'éléments contenus dans une liste :

In [31]:
help(len)
Help on built-in function len in module __builtin__:

len(...)
    len(object) -> integer
    
    Return the number of items of a sequence or mapping.


In [32]:
len(evens)
Out[32]:
10

Itérations, indentations et blocs d'instructions

L'une des choses les plus utiles que vous puissiez faire avec des listes est de les parcourir avec une boucle d'itération, c'est-à-dire de passer en revue les éléments de la liste un à un, l'un après l'autre. En Python, nous utiliserons la syntaxe for … in … pour de telles itérations :

In [33]:
days_of_the_week = ["Sunday", "Monday", 
                    "Tuesday", "Wednesday", 
                    "Thursday", "Friday", "Saturday"]
for day in days_of_the_week:
    print day
Sunday
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday

Ce petit bout de code parcourt chaque élément d'une liste référencée par la variable days_of_the_week et assigne cet élément – lorsque son tour vient – à la variable day. Vient ensuite l'exécution du bloc d'instructions indenté – ici, uniquement la ligne print day – qui se sert de la variable d'itération day. Lorsque le programme a fini de parcourir tous les éléments de la liste, un à un, l'un après l'autre, il sort du bloc d'instructions indenté et continue son chemin.

Presque tous les langages de programmation délimitent les blocs d'instructions d'une manière ou d'une autre. En Fortran, on utilise des déclarations END (ENDDO, ENDIF, etc). En C, C++ et Perl, on utilise des accolades {instruction ; instruction ; instruction…}.

Python, lui, utilise le signe deux-points (:) en fin de ligne suivi d'un retour chariot et d'une indentation fixe pour délimiter de tels blocs d'instructions. Pour rappel, une indentation est un décalage en colonne du code que l'on effectue en insérant soit des tabulations (signe '\t') soit des espaces blanches (en général, quatre espaces par niveau d'indentation) et qui se répète d'une ligne sur l'autre. En Python, toutes les lignes de code qui se succèdent avec un même niveau d'indentation sont ainsi considérées comme appartenant au même bloc d'instructions indenté. Dans l'exemple précédent, nous avions une seule ligne de bloc indenté, mais nous pourrions en avoir plusieurs, exemple :

In [34]:
for day in days_of_the_week:
    statement = "Today is " + day
    print statement
Today is Sunday
Today is Monday
Today is Tuesday
Today is Wednesday
Today is Thursday
Today is Friday
Today is Saturday

La fonction range() s'avère particulièrement utile lorsqu'il s'agit de parcourir des intervalles numériques avec une boucle for … in … :

In [35]:
for i in range(20):
    print "The square of ",i," is ",i*i
The square of  0  is  0
The square of  1  is  1
The square of  2  is  4
The square of  3  is  9
The square of  4  is  16
The square of  5  is  25
The square of  6  is  36
The square of  7  is  49
The square of  8  is  64
The square of  9  is  81
The square of  10  is  100
The square of  11  is  121
The square of  12  is  144
The square of  13  is  169
The square of  14  is  196
The square of  15  is  225
The square of  16  is  256
The square of  17  is  289
The square of  18  is  324
The square of  19  is  361

Échantillonnage

Les listes Python et les chaînes de caractères ont un point commun à peine soupçonnable : toutes deux peuvent être considérées comme itérables, des séquences à parcourir élément par élément. Vous savez déjà que l'on peut parcourir les éléments d'une liste un à un, l'un après l'autre. Vous pouvez faire la même chose avec les lettres d'une chaîne de caractères, lettres qui sont considérées comme les éléments d'une séquence itérable :

In [36]:
for letter in "Sunday":
    print letter
S
u
n
d
a
y

Ce n'est que rarement utile. En revanche, l'échantillonnage d'une séquence, c'est-à-dire l'extraction d'une portion contiguë donnée d'une séquence, l'est nettement plus. Nous savons déjà extraire le premier élément d'une liste grâce à son numéro d'index :

In [37]:
days_of_the_week[0]
Out[37]:
'Sunday'

Maintenant, si nous voulons extraire une portion de liste contenant les deux premiers éléments à partir de la liste référencée par la variable days_of_the_week, nous pouvons utiliser la notation :

In [38]:
days_of_the_week[0:2]
Out[38]:
['Sunday', 'Monday']

ou plus simplement (notation abrégée) :

In [39]:
days_of_the_week[:2]
Out[39]:
['Sunday', 'Monday']

Pour obtenir une portion de liste contenant les derniers éléments de la liste de référence, nous pouvons utiliser un échantillonnage négatif :

In [40]:
days_of_the_week[-2:]
Out[40]:
['Friday', 'Saturday']

ce qui est en accord parfait avec la notation en indices négatifs adressant les derniers éléments de la liste.

Vous pouvez référencer une portion de liste par une variable :

In [41]:
workdays = days_of_the_week[1:6]
print workdays
['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']

Les chaînes de caractères étant elles-mêmes des séquences, vous pouvez tout à fait leur appliquer un échantillonnage, c'est-à-dire en extraire des portions de chaîne – que l'on nomme généralement des sous-chaînes de caractères :

In [42]:
day = "Sunday"
abbreviation = day[:3]
print abbreviation
Sun

Pour finir, nous pouvons passer un troisième argument à notre échantillonnage qui précisera le pas incrémental d'extraction (un peu comme le troisième argument de la fonction range(start, stop, step)) :

In [43]:
numbers = range(0,40)
evens = numbers[2::2]
evens
Out[43]:
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38]

Notez que l'omission du second argument dans l'échantillonnage signifie « jusqu'à la fin » de la liste, ainsi la portion démarre à l'index 2, va jusqu'au dernier élément de liste et avance avec un pas incrémental de 2 en 2, d'où la génération d'une liste de naturels pairs inférieurs à 40.

Booléens et valeurs de vérité

Nous venons de découvrir quelques nouveaux types de données. Nous avons vu les entiers (int), les nombres à virgule flottante (float), les chaînes de caractères (str) et les listes (list ou []), qui sont des structures capables de contenir des éléments. Nous avons aussi vu que les listes pouvaient contenir toute sorte de types de données (y compris d'autres listes). Nous avons appris à afficher des résultats dans la console avec print et à parcourir les éléments d'une séquence itérable un à un, les uns après les autres avec la boucle for … in …. Nous allons à présent découvrir le type booléen (bool) qui ne peut accepter que deux valeurs : True (vrai) et False (faux).

En programmation, nous avons toujours besoin de la notion de condition pour permettre à un programme de s'adapter à différents cas de figure. Si aujourd'hui est lundi alors je dois aller travailler, mais si c'est dimanche alors je peux rester faire la grasse matinée. Pour pouvoir accomplir ce genre de choses en Python, on fait appel à des expressions booléennes qui ne peuvent revêtir que deux possibilités – ou bien l'expression est vraie ou bien elle est fausse – et à la syntaxe if condition: qui permet de contrôler le flux du programme grâce à ces valeurs booléennes.

Par exemple :

In [44]:
if day == "Sunday":
    print "Sleep in"
else:
    print "Go to work"
Sleep in

Devinette : pourquoi ce bout de code résulte par « Sleep in » ? Quelle est la valeur de la variable day au moment de son évaluation dans l'expression booléenne de if ?

Voyons tout cela plus en détail. Tout d'abord, notez l'expression booléenne :

In [45]:
day == "Sunday"
Out[45]:
True

En l'évaluant seule – comme nous venons de le faire – nous nous apercevons que cette expression est vraie (True). L'opérateur double-égal (==) effectue un test d'égalité entre les opérandes placés de part et d'autre. Si ses opérandes sont égaux, l'expression a la valeur True (vrai) et sinon, l'expression a la valeur False (faux). Dans notre exemple, l'opérateur compare d'un côté le contenu de la variable day et de l'autre la valeur intrinsèque de la chaîne de caractères "Sunday". Comme visiblement la variable day doit contenir la chaîne de caractères "Sunday", le test d'égalité obtient logiquement la valeur de vérité True (vrai).

La déclaration if condition: se termine par un caractère deux-points (:) ce qui implique qu'un bloc d'instructions indenté devra se trouver à la ligne suivante. Si l'expression condition est évaluée à True (vrai), c'est ce bloc indenté qui sera exécuté. Dans le cas contraire, si l'expression est évaluée à False (faux), ce bloc d'instructions indenté sera tout bonnement ignoré. En reprenant notre exemple, comme l'expression est évaluée à True, c'est bien le bloc indenté print "Sleep in" qui est exécuté, d'où le résultat final.

Le premier bloc d'instructions indenté est suivi par la déclaration else: qui exécutera le bloc d'instructions indenté se trouvant à la ligne suivante si tout ce qui a été évalué auparavant s'est avéré faux (False). Dans notre exemple, le test d'égalité s'étant avéré True (vrai), le bloc d'instructions indenté qui suit else: sera tout bonnement ignoré.

En Python, vous pouvez comparer toute sorte d'expressions et toute sorte de types de données :

In [46]:
1 == 2
Out[46]:
False
In [47]:
50 == 2*25
Out[47]:
True
In [48]:
3 < 3.14159
Out[48]:
True
In [49]:
1 == 1.0
Out[49]:
True
In [50]:
1 != 0
Out[50]:
True
In [51]:
1 <= 2
Out[51]:
True
In [52]:
1 >= 1
Out[52]:
True

Vous noterez la présence de nouveaux opérateurs de comparaison – tous booléens – comme strictement inférieur (<), inférieur ou égal (<=), supérieur ou égal (>=) ou encore différent de (!=).

Le test 1 == 1.0 évalué à True (vrai) est particulièrement intéressant : les opérandes 1 (int) et 1.0 (float) ne sont pas du même type de données ; en revanche, ils sont de même valeur numérique. Pour comparer le type de données d'un objet plutôt que sa valeur intrinsèque, il existe l'opérateur booléen is (et son pendant logique is not) :

In [53]:
1 is 1.0
Out[53]:
False

On peut aussi comparer des listes entre elles :

In [54]:
[1,2,3] == [1,2,4]
Out[54]:
False
In [55]:
[1,2,3] < [1,2,4]
Out[55]:
True

Pour finir, vous pouvez assembler des opérateurs de comparaison ensemble dans une même expression, ce qui produit des notations intuitives :

In [56]:
hours = 5
0 < hours < 24
Out[56]:
True

La syntaxe if condition: bloc else: bloc n'est complète qu'avec l'adjonction des clauses alternatives elif condition: (elif est une abréviation de else if) entre if et else, clauses qui permettent d'évaluer plusieurs cas de figure en cascade. Par exemple :

In [57]:
if day == "Sunday":
    print "Sleep in"
elif day == "Saturday":
    print "Do chores"
else:
    print "Go to work"
Sleep in

Bien sûr, nous pouvons mélanger des conditions if … avec des boucles for … in … afin d'obtenir quelque chose de – presque – intéressant :

In [58]:
for day in days_of_the_week:
    statement = "Today is " + day
    print statement
    if day == "Sunday":
        print "   Sleep in"
    elif day == "Saturday":
        print "   Do chores"
    else:
        print "   Go to work"
Today is Sunday
   Sleep in
Today is Monday
   Go to work
Today is Tuesday
   Go to work
Today is Wednesday
   Go to work
Today is Thursday
   Go to work
Today is Friday
   Go to work
Today is Saturday
   Do chores

Ceci relève peut-être d'un sujet pointu, mais les types de données natifs Python ont tous une valeur booléenne qui leur est associée et – à vrai dire – dans les toutes premières versions de Python, il n'y avait pas de type booléen (bool) à proprement parler. En gros, tout ce qui pouvait s'évaluer à zéro (0) était considéré faux (False) et le reste était considéré vrai (True). Vous pouvez extraire la valeur booléenne d'une expression ou d'un type de données (objet) grâce à la fonction bool(x).

In [59]:
bool(1)
Out[59]:
True
In [60]:
bool(0)
Out[60]:
False
In [61]:
bool(["This "," is "," a "," list"])
Out[61]:
True

Exemple de code : la suite de Fibonacci

En mathématiques, la suite de Fibonacci est une suite d'entiers dans laquelle chaque terme est la somme des deux termes qui le précèdent. Elle commence généralement par les termes 0 et 1 (parfois 1 et 1) et ses premiers termes sont 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, etc.

Dans les livres qui traitent de programmation, on rencontre fréquemment cet exercice qui consiste à coder la série de Fibonacci pour obtenir les valeurs de n termes. Voyons tout d'abord le code, nous discuterons par la suite de sa signification.

In [62]:
n = 10
sequence = [0,1]
# cette boucle pourrait poser problème pour n <= 2
for i in range(2,n):
    sequence.append(sequence[i-1]+sequence[i-2])
print sequence
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

On commence par initialiser une variable nommée n avec la valeur entière 10. Ici, la variable n représente le nombre de termes à calculer. Ensuite, on affecte une liste Python [0, 1] à une variable nommée sequence, ces deux valeurs initiales correspondant aux termes de rang 0 et 1 de la suite (F0 = 0 et F1 = 1). Les valeurs initiales doivent être déclarées manuellement car la formulation de la série de Fibonacci requiert la somme des deux termes précédant le terme calculé.

Le calcul commence avec la boucle itérative for … in … qui affecte à une variable nommée i des valeurs entières allant de 2 (inclus) à n (exclus). Notez en passant la présence d'un commentaire qui signale un éventuel problème si l'on initialise n à une valeur n <= 2. Un commentaire Python se caractérise par une phrase humainement intelligible commençant toujours par le signe dièse (#) et se terminant en bout de ligne par le retour chariot (saut à la ligne, CR, '\n'), tout simplement. Tout ce qui se trouve entre ce signe dièse (#) et la fin de la ligne est ignoré par l'interpréteur Python, vous pouvez donc y inscrire tout ce que vous souhaitez mentionner, y compris des portions de code à ne pas exécuter. Bien pratique pour désactiver temporairement du code. En programmation, un code source est appelé à être lu et relu bien plus souvent qu'écrit, il est donc nécessaire d'indiquer à certains points critiques ce que vous avez fait et d'y expliquer pourquoi vous l'avez fait, quels sont les risques, les problèmes, les indications pour les développeurs qui vous succèderont dans la maintenance de ce code, etc. Dans notre exemple, nous avons signalé un problème que nous aurions facilement pu résoudre avec une condition if condition, ce que nous verrons un peu plus tard.

Dans le bloc d'instructions indenté de la boucle for … in …, nous ajoutons à la liste Python référencée par la variable sequence ce qui correspond à la somme des deux termes immédiatement précédents, à savoir les termes d'indices [i-1] et [i-2], ce qui est bien conforme à la formulation de la suite de Fibonacci.

En sortie de boucle, lorsque tous les calculs sont effectués, nous affichons le résultat final, c'est-à-dire la liste Python référencée par la variable sequence dans son intégralité. Et voilà le travail !

Fonctions

Nous aimerions à présent pouvoir utiliser ce bout de code pour générer des listes de valeurs de Fibonacci avec divers nombres d'éléments, à la demande. Créons une fonction paramétrique que nous appellerons fibonacci() – notez la présence de parenthèses qui permettent de distinguer une fonction d'une variable simple – et plaçons-y tout le code dont nous avons besoin. Pour créer une fonction Python, il suffit d'utiliser le mot-clé réservé def en début d'instruction selon la syntaxe def nom_fonction (paramètres):.

In [63]:
def fibonacci (sequence_length):
    """
        retourne une liste de valeurs calculées
        selon la suite de Fibonacci comprenant
        sequence_length éléments ;
    """
    sequence = [0,1]
    if sequence_length < 1:
        print "La suite de Fibonacci commence avec au moins 1 élément"
        return
    if 0 < sequence_length < 3:
        return sequence[:sequence_length]
    for i in range(2,sequence_length): 
        sequence.append(sequence[i-1]+sequence[i-2])
    return sequence

Nous pouvons à présent faire appel à cette fonction fibonacci() avec différentes valeurs pour le paramètre sequence_length. Pour appeler une fonction, il suffit de la mentionner – sans le mot-clé def – avec ses parenthèses et ses valeurs de paramètres, s'il y a lieu.

In [64]:
fibonacci(2)
Out[64]:
[0, 1]
In [65]:
fibonacci(12)
Out[65]:
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

Nous venons de découvrir plusieurs nouveautés. Tout d'abord, notez que la définition d'une fonction en Python s'écrit avec la même syntaxe que pour les blocs d'instructions indentés – la phrase def nom_fonction(paramètres): se termine bien par un signe deux-points (:) qui indique l'obligation d'écrire un bloc d'instructions indenté à la ligne suivante. On retrouve cette logique partout dans la syntaxe Python. Ensuite, vous remarquerez que la fonction commence par un texte délimité par des guillemets anglais triples (""") : il s'agit d'un docstring, un texte explicatif spécial qui permet de documenter automatiquement un module, une classe ou une fonction lors de l'appel de la fonction Python help(), ce qui peut s'avérer fort pratique pour les utilisateurs de votre code.

In [66]:
help(fibonacci)
Help on function fibonacci in module __main__:

fibonacci(sequence_length)
    retourne une liste de valeurs calculées
    selon la suite de Fibonacci comprenant
    sequence_length éléments ;


Plus vous documentez vos modules, classes et fonctions avec leurs docstring respectifs – toujours placés en début de bloc d'instructions indenté – et plus vous rendez votre code exploitable pour d'autres utilisateurs.

Pour finir, notez aussi que dans ce code, nous avons préféré remplacer le commentaire qui signalait un problème par une série de tests if condition: avec leurs blocs d'instructions indentés qui traitent le problème et qui affichent un message à l'attention de l'utilisateur, le cas échéant.

NdT : une fonction produit un résultat avec le mot-clé réservé return qui oblige l'interpréteur à quitter le bloc de code définissant une fonction en retournant le résultat de l'expression mentionnée à la suite de ce mot-clé – exemple : return (x+3) – à l'instruction qui a appelé cette fonction (l'appelant). Notez aussi que l'on peut mentionner plusieurs fois le mot-clé return dans une définition de fonction, à divers endroits du code et selon les besoins de contrôle du flux du programme.

Récursivité et factorielles

Un aspect intéressant de la définition des fonctions est la capacité d'une fonction à s'appeler elle-même. On appelle ce concept la récursivité. Expérimentons la récursivité grâce à la fonction factorielle, qui se définit pour un entier naturel \(n\) par \(n!= n\times(n-1)\times(n-2)\times\cdots\times 2\times 1\) avec comme cas particulier \(0!=1\).

Tout d'abord, notez que nous n'avons pas réellement besoin d'écrire cette fonction en temps normal, puisqu'elle existe déjà dans la librairie math fournie d'office avec Python. Voyons ce que nous dit la fonction help() à ce sujet :

In [67]:
from math import factorial
help(factorial)
Help on built-in function factorial in module math:

factorial(...)
    factorial(x) -> Integral
    
    Find x!. Raise a ValueError if x is negative or non-integral.


C'est clairement ce que nous voulons.

In [68]:
factorial(20)
Out[68]:
2432902008176640000

Toutefois, si nous voulions coder cette fonction par nous-mêmes, nous pourrions remarquer que \(n!= n\times(n-1)\times(n-2)\times\cdots\times 2\times 1 \Leftrightarrow n!=n\times(n-1)!\), ce qui reviendrait à écrire sous forme de code Python :

In [69]:
def fact (n):
    if n <= 0:
        return 1
    return n*fact(n-1)
In [70]:
fact(20)
Out[70]:
2432902008176640000

La récursivité peut être mathématiquement très élégante et permet en outre de produire des programmes très simples.

NdT : la récursivité s'avère néanmoins particulièrement gourmande en ressources mémoire et en temps machine, notamment lorsqu'il s'agit d'effectuer un très grand nombre d'itérations récursives.

Deux nouvelles structures de données : tuples et dictionnaires

Avant de terminer notre présentation du langage Python, je voudrais signaler deux autres structures de données très pratiques – et donc très répandues – parmi les programmes Python : le tuple et le dictionnaire.

Un tuple (tuple ou ()) est un objet itérable au même titre que le sont les listes Python (list ou []) et les chaînes de caractères (str). Un tuple s'écrit en regroupant divers objets dans une liste séparée par des virgules et généralement délimitée par des parenthèses (certains cas autorisent une notation sans parenthèses, NdT : mais cela reste peu recommandé) :

In [71]:
t = (1, 2, 'hi', 9.0)
t
Out[71]:
(1, 2, 'hi', 9.0)

Tout comme les listes Python, vous pouvez accéder aux éléments d'un tuple par la notation en indices :

In [72]:
t[1]
Out[72]:
2

Toutefois, contrairement aux listes Python, les tuples sont dits immutables (immuables), ce qui signifie que vous ne pouvez ni les agrandir ni modifier la position des éléments ni même modifier les éléments eux-mêmes. En somme, un tuple est un objet fixe, non modifiable, qui s'utilise tel qu'il a été défini (en lecture seule) :

In [73]:
t.append(7)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-73-50c7062b1d5f> in <module>()
----> 1 t.append(7)

AttributeError: 'tuple' object has no attribute 'append'
In [74]:
t[1]=77
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-74-03cc8ba9c07d> in <module>()
----> 1 t[1]=77

TypeError: 'tuple' object does not support item assignment

Les tuples sont utiles chaque fois que vous souhaitez regrouper des objets au sein d'une même structure sans être pour autant obligés de recourir à une définition de classe (voir plus bas). Par exemple, supposons que vous ayez besoin de coordonnées cartésiennes pour certains objets dans votre programme. Les tuples sont un bon moyen d'effectuer ce regroupement :

In [75]:
('Bob',0.0,21.0)
Out[75]:
('Bob', 0.0, 21.0)

À nouveau, certes ce n'est pas obligatoire, mais une bonne façon de distinguer un tuple d'une liste Python reste de considérer qu'un tuple est un regroupement de données hétérogènes (ici, un nom et des coordonnées x et y), là où une liste Python devrait plutôt être une collection d'objets identiques, comme par exemple ceci :

NdT : je répète, ces considérations de l'auteur sont plus que discutables car elles ne caractérisent en rien les listes et les tuples selon les définitions Python qui en sont faites, ni même l'usage que l'on devrait supposément en faire.

In [76]:
positions = [
             ('Bob',0.0,21.0),
             ('Cat',2.5,13.1),
             ('Dog',33.0,1.2)
             ]

On peut utiliser les tuples lorsqu'une fonction a par exemple besoin de retourner plus d'une valeur à la fois. Supposons que nous voulions déterminer la plus petite valeur des coordonnées x et y présentes dans la liste positions définie précédemment. Nous pourrions écrire :

In [77]:
def minmax(objects):
    minx = 1e20 # ceci représente un très grand nombre
    miny = 1e20
    for obj in objects:
        name,x,y = obj
        if x < minx: 
            minx = x
        if y < miny:
            miny = y
    return minx,miny

x,y = minmax(positions)
print x,y
0.0 1.2

Nous venons de faire deux choses que nous n'avons pas encore vues. Tout d'abord, nous avons déployé la valeur de retour d'une fonction dans plusieurs variables en une seule fois, en nous servant du mécanisme d'initialisation par tuple :

name, x, y = tuple_values

Ensuite, avec return minx,miny en fin de définition de fonction, nous retournons le tuple (minx, miny) à l'appelant x, y = minmax(positions), ce qui revient à faire une initialisation par tuple x, y = (minx, miny) au bout du compte. Ceci aurait été bien compliqué à mettre en œuvre en C++, par exemple.

L'initialisation par tuple permet en outre d'interchanger les valeurs de deux variables en une seule opération (swap) :

In [78]:
x,y = 1,2
x,y = y,x
x,y
Out[78]:
(2, 1)

Les dictionnaires Python (dict ou {}) sont des structures de données que l'on retrouve dans d'autres langages sous des appellations comme « mappages » ou encore « tableaux associatifs ». Là où une liste Python accède à ses éléments avec un index numérique :

In [79]:
mylist = [1,2,9,21]
mylist[2]
Out[79]:
9

L'index d'un dictionnaire est appelé une clé, et l'entrée qui lui est associée une valeur. Un dictionnaire admet toute sorte d'objets en tant que clé d'indexation. Les dictionnaires suivent la notation {clé : valeur, clé : valeur, ...}, exemple :

In [80]:
ages = {"Rick": 46, "Bob": 86, "Fred": 21}
print "Rick's age is ",ages["Rick"]
Rick's age is  46

On peut aussi créer un dictionnaire grâce aux arguments nommés :

In [81]:
dict(Rick=46,Bob=86,Fred=20)
Out[81]:
{'Bob': 86, 'Fred': 20, 'Rick': 46}

La fonction len() permet de compter les éléments de tout itérable ; cela comprend les listes (list), les chaînes de caractères (str), les tuples (tuple) et bien entendu, les dictionnaires (dict) :

In [82]:
len(t)
Out[82]:
4
In [83]:
len(ages)
Out[83]:
3

Tracer des graphiques avec Matplotlib

Nous pouvons généralement mieux appréhender les différentes tendances des séries de données en les visualisant avec un graphique. Python dispose d'une excellente librairie pour faciliter le tracé de graphiques : Matplotlib. L'interface IPython Notebook que nous utilisons actuellement intègre d'office cette librairie graphique.

Durant nos exemples, nous avons expérimenté deux fonctions : la suite de Fibonacci et la factorielle. Toutes deux croissent plus vite qu'un polynôme. Mais laquelle croît le plus vite ? Traçons-les. Pour commencer, générons une liste de 10 valeurs de Fibonacci :

In [84]:
fibs = fibonacci(10)

Ensuite, récupérons une liste de 10 valeurs de factorielle :

In [85]:
facts = []
for i in range(10):
    facts.append(factorial(i))

À présent, utilisons la fonction plot() de la librairie Matplotlib pour représenter toutes ces valeurs :

In [86]:
figsize=(8,6)
plot(facts,label="factorial")
plot(fibs,label="Fibonacci")
xlabel("n")
legend()
Out[86]:
<matplotlib.legend.Legend at 0x7f7044612cd0>

La fonction factorielle croît nettement plus vite que la suite de Fibonacci. À vrai dire, on n'arrive même pas à visualiser la courbe des valeurs de Fibonacci sur le graphique. Ce n'est pas vraiment surprenant : une fonction qui multiplie par n chaque itération est appelée à croître plus rapidement qu'une fonction à laquelle l'on ajoute à peu de chose près n à chaque itération.

NdT : on remarquera toutefois que d'après la formule de Binet, la suite de Fibonacci semble se comporter comme une suite géométrique de raison \(\varphi=\frac{1+\sqrt5}{2}\approx 1,618033988749\) (nombre d'or).

Utilisons une échelle semi-logarithmique pour visualiser ces courbes plus clairement :

In [87]:
semilogy(facts,label="factorial")
semilogy(fibs,label="Fibonacci")
xlabel("n")
legend()
Out[87]:
<matplotlib.legend.Legend at 0x7f704441f910>

On peut faire énormément de choses avec Matplotlib. Nous verrons tout cela dans les prochaines rubriques. En attendant, si vous voulez vous faire une idée des capacités de Matplotlib, jetez un œil à la galerie officielle. Le document IPython notebook de Rob Johansson, Introduction to Matplotlib est lui aussi particulièrement intéressant.