Premiers pas en Python

Utilisation de Python

Python, comme la majorité des langages dit de script, peut être utilisé aussi bien en mode interactif qu’en mode script / programme.

Dans le premier cas, il y a un dialogue entre l’utilisateur et l’interprète: les commandes entrées par l’utilisateur sont évaluées au fur et à mesure. Cette première solution est pratique pour réaliser des prototypes, ainsi que pour tester tout ou partie d’un programme ou plus simplement pour interagir aisément et rapidement avec des structures de données complexes. Le listing suivant présente comment lancer l’interprète (simplement en tapant python à l’invite du shell [1]) et comment en sortir (en tapant Ctrl-D).

$ python
Python 2.7.2 (v2.7.2:8527427914a2, Jun 11 2011, 15:22:34)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> print 'hello world!'
hello world!
>>> ^D
$

Pour une utilisation en mode script les instructions à évaluer par l’interprète sont sauvegardées, comme n’importe quel programme informatique, dans un fichier. Dans ce second cas, l’utilisateur doit saisir l’intégralité des instructions qu’il souhaite voir évaluer à l’aide de son éditeur de texte favori, puis demander leur exécution à l’interprète. Les fichiers Python sont identifiés par l’extension .py. Le listing suivant, l’interprète est appelé avec en paramètre le nom du programme à évaluer (le traditionnel hello world’). Dans le cas d’un système Unix, la première ligne du fichier ``hello.py` précise quel interprète utiliser pour évaluer le fichier si ce dernier est exécutable. Il suffit dans ce cas de taper le nom du fichier à l’invite du shell.:

$ cat hello.py
#! /usr/bin/env python
print 'hello world!'
$ python hello.py
hello world!

Structures de base

Commentaires

Comme dans la majorité des langages de script, les commentaires Python sont définis à l’aide du caractère #. Qu’il soit utilisé comme premier caractère ou non, le # introduit un commentaire jusqu’à la fin de la ligne. Comme toujours, les commentaires sont à utiliser abondamment avec parcimonie. Il ne faut pas hésiter à commenter le code, sans pour autant mettre des commentaires qui n’apportent rien. Le listing suivant présente une ligne de commentaire en Python. Les commentaires seront de nouveaux abordés en [sub:chap1:doc] pour l’auto-documentation. Les commentaires introduits par # devraient être réservés au remarques sur le code en sa mise en oeuvre.

>>> # ceci est un commentaire
>>> print 'bouh' # ceci est aussi un commentaire
bouh

Typage en Python

En Python, tout est objet. Quelque soit les données que l’on manipule, ces données sont des objets dont les classes sont définies par l’usager, ou par l’environnement Python pour les types de base. Une conséquence est que l’utilisateur manipule les données au travers de références (qui donnent accès aux fonctionnalités des instances). Cette approche permet de rendre homogène les données manipulées par l’utilisateur (comme c’était le cas en SmallTalk). Ainsi, toutes les données suivantes suivant sont des objets: 1, [2, 3, 4], 5.6, ‘toto’, une instance de Foo.

Python est un langage à typage dynamique. Ceci ne veut pas dire que les données que l’on manipule ne sont pas typées, mais que leur type est «calculé» lors de leur utilisation [2]. Dans ce contexte, le type des variables n’est pas défini explicitement par l’utilisateur. Ainsi, une même variable peut référencer dans un programme des objets de types différents [3].

>>> x = 1       # x reference un entier
>>> x = 'toto'  # x reference desormais une chaine
>>> x = Foo()   # x reference desormais une instance de Foo

Arithmétique

Python permet d’exprimer très simplement des opérations arithmétiques. Dans le cas où tous les opérandes sont des entiers, alors les résultats seront aussi des entiers. Lorsqu’au moins un des opérandes est de type réel, alors tous les opérandes sont automatiquement convertis en réels.

>>> x = 1 + 2
>>> y = 5 * 2
>>> y / x
3
>>> y % x
1
>>> y = 5.5 * 2
>>> y
11.0
>>> x = 12.0 / 3
>>> x
4.0

Dans le contexte de l’arithmétique, les affections peuvent prendre deux formes. Ces deux formes ont un sens différent. Le listing suivant présente deux affectations qui pourraient être comprises de manière identique. Toutefois, la première forme (ligne 2) a pour conséquence de créer une nouvelle instance d’entier pour contenir l’ajout de 2 à la valeur de x. La seconde forme (ligne 3) ajoute 2 à la valeur de x sans créer de nouvelle instance. La manière d’écrire une opération a donc un impact sur son évaluation.

>>> x = 4
>>> x = x + 2
>>> x += 2

Chaînes de caractères

Les chaînes de caractères se définissent de plusieurs manières en Python. Il est possible d’utiliser indifféremment des guillemets simples ou des guillemets doubles. Le choix est souvent imposé par le contenu de la chaîne: une chaîne contenant des guillemets simples sera déclarée avec des guillemets doubles et réciproquement. Pour les autres cas, c’est indifférent. Enfin, comme tout est objet en Python une chaîne est donc un objet. Le listing suivant déclarer deux chaînes référencées par x et y. Enfin, la chaîne référencée par z est une chaîne de caractères multi-lignes (utilisation de trois quotes / guillemets simples ou doubles).

>>> x = 'hello '
>>> y = "world!"
>>> z = '''hello
... world'''

Concaténation

La concaténation de ces chaînes de caractères peut prendre deux formes. Dans les deux cas, l’opérateur + est utilisé pour exprimer la concaténation. La forme de la ligne 4 est un raccourci d’écriture.

>>> z = x + y
>>> z
'hello world!'
>>> x += y
>>> x
'hello world!'

Affichage

L’affichage de chaînes de caractères à l’aide de la fonction print peut se faire en concaténant explicitement des chaînes (que ce soit avec l’opérateur de concaténation ou en utilisant des virgules) ou en utilisant une chaîne de formatage comme la fonction printf du langage C. Cette seconde option est un peu plus puissante, mais aussi un peu plus lourde à utiliser. Le listing suivant présente trois manière d’afficher des chaîne de caractères.

>>> print 'I say: ' + x
I say: hello world!
>>> print x, 2, 'times'
hello world! 2 times
>>> print "I say: %s %d time(s)" % (x, 2)
I say: hello world! 2 time(s)

Manipulations

Python offre une méthode simple pour accéder aux caractères contenus dans une chaîne: une chaîne est manipulée comme une séquence indexée de caractères. Ainsi, chaque caractère est accessible directement par son index (le premier étant indexé 0) en utilisation des crochets. En plus de cet accès unitaire aux caractères, il est possible d’accéder à des sous-chaînes en précisant la tranche souhaitée l’index de début (qui est inclus) étant séparé de l’index de fin (qui est exclu) par le caractère :. Dans le cas des sous-chaînes, la valeur fournie est une copie et non un accès à une partie de la chaîne d’origine.

Le listing suivant donne quelques exemples d’accès à un caractère (ligne 2), ou à une sous-chaîne pour le reste. La colonne de gauche présente des accès à partir du début de la chaînes (les index sont positifs). La ligne 6 signifie que l’on souhaite le contenu de x du quatrième caractère à la fin. Enfin, la dernière ligne réalise une copie de la chaîne x.

>>> x = 'hello world!'
>>> x[4]
'o'
>>> x[2:4]
'll'
>>> x[3:]
'lo world!'
>>> x[:]
'hello world!'

Enfin, un index négatifs précise que le calcul s’effectue depuis la fin de la chaîne.

>>> x[-3:]
'ld!'
>>> x[1:-1]
'ello world'

Listes

Les listes Python sont des ensemble ordonnés et dynamique d’éléments. Ces ensemble peuvent contenir des éléments de différents types, leur seul point commun est que ce sont des objets. Et comme tout est objet, les listes sont elles mêmes des objets (instances de la classe list).

L’exemple suivant crée tout d’abord deux listes vides avec les deux manières possibles.

>>> mylist = []
>>> mylist2 = list()

Ensuite, après avoir défini x, une liste contenant la chaîne ‘bar’, l’entier 12345 et l’objet référencé par la variable x est créée.

>>> x = True
>>> foo = ['bar', 12345, x]
>>> foo
['bar', 12345, True]

Les listes et les chaînes ont pour point commun le fait d’être des ensembles ordonnés. L’accès à un élément d’une liste se fait donc aussi par indexation (ligne 1). Il est possible de prendre une partie de liste (ligne 3) et d’obtenir une copie de liste (ligne 5). Une copie de liste crée une nouvelle liste, mais partage les éléments contenus dans la liste d’origine.

Cette dernière manipulation est très importante lors de parcours de listes (voir section [subChap1LoopPbl]) pour ne pas modifier la liste qui est parcourue et se retrouver dans une boucle sans fin.

>>> foo[2]
True
>>> foo[1:]
[12345, True]
>>> bar = foo[:]

Enfin, le contenu de la liste peut être changé par simple affection en utilisant l’index cible. Comme la liste référencée par bar est une copie de la liste référencée par foo, le dernier élément de bar n’est pas modifié par l’affectation du dernier élément de foo.

>>> foo[2] = 1
>>> foo
['bar', 12345, 1]
>>> bar[-1]
True

L’ajout d’éléments dans une liste se fait à l’aide des méthodes append pour un ajout en fin de liste, et insert, pour un ajout à un index donné. Enfin, la méthode extend ajoute le contenu d’une liste passé en paramètre à la fin de la liste.

>>> foo.append('new')
>>> foo
['bar', 12345, 1, 'new']
>>> foo.insert(2, 'new')
>>> foo
['bar', 12345, 'new', 1, 'new']
>>> foo.extend([67, 89])
>>> foo
['bar', 12345, 'new', 1, 'new', 67, 89]

La méthode index permet de connaître l’index de la première occurrence d’un élément dans une liste. Dans le cas où l’élément fournit en paramètre n’est pas présent, la méthode lève l’exception ValueError. L’utilisation de la construction in retourne quant à elle True si l’élément est présent dans la liste et False sinon.

>>> foo.index('new')
2
>>> foo.index(34)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ValueError: list.index(x): x not in list
>>> 34 in foo
False

Les listes peuvent être fusionnées par concaténation. Ces concaténations peuvent aussi bien se faire par copie que par ajout. La ligne 1 représente une concaténation par copie des deux listes existantes. La ligne 4 présente une concaténation par ajout d’éléments à une liste existante. Enfin, la ligne 7 présente une manière simple de créer une liste par répétition d’un motif qui doit être lui même une liste.

>>> bar = [0, 1] + [1, 0]
>>> bar
[0, 1, 1, 0]
>>> bar += [2, 3]
>>> bar
[0, 1, 1, 0, 2, 3]
>>> [0, 1] * 3
[0, 1, 0, 1, 0, 1]

Attention Dans le cas d’une création de liste par répétition, les éléments ne sont pas dupliqués, mais ce sont leurs références qui sont répétées (et donc présentes plusieurs fois dans la liste finale). Utiliser l’opérateur * pour la construction de matrice est donc une mauvaise idée car toutes les lignes seraient alors la même liste référencée plusieurs fois.

>>> matrix = [[1, 2, 3]] * 3
>>> matrix
[[1, 2, 3], [1, 2, 3], [1, 2, 3]]
>>> matrix[1][1] = 4
>>> matrix
[[1, 4, 3], [1, 4, 3], [1, 4, 3]]

De manière symétrique à l’ajout d’éléments dans une liste, il est possible d’en supprimer. La méthode remove permet de supprimer la première occurrence d’un élément d’une liste en le désignant.

>>> foo.remove('new')
>>> foo
['bar', 12345, 1, 'new', 67, 89]

Si l’élément fourni en paramètre n’existe pas dans la liste, l’exception ValueError est levée.

>>> foo.remove(34)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ValueError: list.remove(x): x not in list

L’opérateur del (delete) permet de détruire une référence à un objet Python, ou à une partie d’une liste [4].

>>> del foo[1:3]
>>> foo
['bar', 'new', 67, 89]

D’autres formes plus avancées de manipulation de listes sont présentées dans la section [sub:chap1:listes].

Listes et chaînes de caractères

Les listes et les chaînes de caractères sont similaire dans leur structure et dans leur manipulation. Certaines méthodes disponibles sur les chaînes manipulent les deux structures de données.

La méthode join disponible sur les chaîne permet de construire une chaîne de caractère depuis une liste de chaînes. La chaîne sur laquelle est invoquée la méthode join est alors utilisé comme séparateur des différents éléments de la liste.

>>> ' ; '.join(['a', 'b', 'c'])
'a ; b ; c'

De manière symétrique, la méthode split, disponible sur les chaînes, permet de décomposer une chaîne de caractères en une liste de sous chaînes. Ce découpage se fait par rapport à un ou plusieurs caractères. Dans le cas où un entier est passé comme second argument, il précise le nombre maximum de découpage.

>>> 'hello crazy world!'.split(" ")
['hello', 'crazy', 'world!']
>>> 'hello crazy world!'.split(" ", 1)
['hello', 'crazy world!']

Tuples

Les tuples sont des ensemble ordonnés et immuables d’éléments. Comme les listes, les tuples peuvent contenir des données de différents types. La première ligne présente une déclaration classique (avec des parenthèses) alors que la secone ligne présente la notation abrégée. La virgule est importante pour préciser que l’on parle d’un tuple à un élément et non de la valeur 12. Cette remarque serait valable dans la cas d’une déclaration parenthésée d’un tuple à un élément.

>>> foo = ('bar', 12345, x)
>>> bar = 12,

Comme les listes, les tuples sont accessibles par indexation, et la construction in permet de tester la présence d’un élément dans le tuple. Cependant, une fois créé le contenu d’un tuple ne peut être modifié.

>>> foo[1]
12345
>>> foo[:-1]
('bar', 12345)
>>> 'bar' in foo
True
>>> foo[1]  = 12
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

Remarque Critères de choix entre une liste et un tuple:

  • les tuples sont plus rapides à parcourir que les listes,
  • pour définir des constantes, utiliser des tuples.

Il est possible de convertir des tuples vers des listes et réciproquement.

>>> list((1, 2, 3))
[1, 2, 3]
>>> foo = tuple([1, 2, 3])
>>> foo
(1, 2, 3)

Pour terminer, Python offre l’affectation multiple pour assigner différentes valeurs depuis un tuple de manière simultanée. Ici encore, les parenthèses peuvent être omises. Il est aussi possible de faire des assignation en cascade.

>>> (x, y, z) = foo
>>> x
1
>>> a, b, c = foo
>>> b
2
>>> d = e = b
>>> d
2
>>> e
2

Dictionnaires

Les dictionnaires, parfois appelés tableaux associatifs, sont des ensembles non ordonnés d’éléments indexés par des clés. Une clé doit obligatoirement être immuable (chaîne, entier ou tuple). D’autre part, une clé est toujours unique.

Un dictionnaire est déclaré par deux accolades ou en utilisant le type dict. Il peut être créé vide ou avec des valeurs initiales.

>>> dict1 = {}
>>> dict2 = {'foo': 456, 123: 'bar'}

>>> dict3 = dict()
>>> dict4 = dict(foo=456, bar=123)

L’ajout et l’accès aux éléments d’un dictionnaire se fait par indexation sur la clé associé à l’élément.

>>> dict1['foo'] = 456
>>> dict1[123] = 'bar'

>>> dict1[123]
'bar'

L’affichage d’un dictionnaire présente une liste de paires «clé: valeur».

>>> dict1
{123: 'bar', 'foo': 456}
>>> dict4
{'foo': 456, 'bar': 123}

L’utilisation de la classe dict pour créer un dictionnaire non vide, ne peut se faire qu’avec des chaines (utilisation d’arguments nommés).

>>> dict4
{'foo': 456, 'bar': 123}

Les dictionnaires offrent des méthodes pour manipuler les clés. La méthode keys retourne une liste de toutes les clés du dictionnaire et la méthode has_key retourne True si la clé donné en paramètre est présente dans le dictionnaire et False dans le cas contraire. Il est aussi possible d’utiliser l’opérateur in

>>> dict1.keys()
[123, 'foo']

>>> dict1.has_key('bar')
False

>>> 123 in dict1
True

La méthode values donne accès à une liste des valeurs contenues dans le dictionnaire et la méthode items donne une liste de tuples, contenant chacun une paire clé, valeur.

>>> dict1.values()
['bar', 456]
>>> dict1.items()
[(123, 'bar'), ('foo', 456)]

La modification d’une valeur associée à une clé se fait simplement en affectant de nouveau la valeur indexée dans le dictionnaire par la clé en question. Enfin, l’opérateur del permet de supprimer une association du dictionnaire.

>>> dict1[123] = 789
>>> dict1
{123: 789, 'foo': 456}
>>> del dict1['foo']
>>> dict1
{123: 789}

A l’aides des méthodes de manipulation des clé et des valeurs, il est possible de parcourir un dictionnaire de plusieurs manières. Les quelques lignes suivantes donnent deux exemples de parcours.

>>> flames = {'windows': 'bof', 'unix': 'cool'}
>>> for key in flames.keys():
...     print key, 'is', flames[key]
...
windows is bof
unix is cool
>>> for key, value in flames.items():
...     print key, 'is', value
...
windows is bof
unix is cool

Constructions

Structuration et indentation

La structuration d’un programme Python est définie par son indentation. Le début d’un bloc est défini par un ‘:‘, la première ligne pouvant être considérée comme un en-tête (test, boucle, définition, etc.). Le corps du bloc est alors indenté de manière plus importante (mais régulière) que l’en-tête. Enfin, la fin du bloc est délimité par le retour à l’indentation de l’en-tête. La convention en Python est d’utiliser quatre espaces pour chaque niveau d’indentation. Les bloc peuvent être imbriqués.

<en-tete>:
    <instructions>

Dans le cas de bloc de taille réduite, par exemple une seule instruction, un bloc peut être défini sur une seule ligne. Le caractère : sert toujours à délimiter l’en-tête du corps. Cependant, cette utilisation n’est pas vraiment bénéfique quant à la lisibilité du code, si ce n’est pour faire tenir du code sur un transparent. Elle est donc à éviter, l’époque où les caractères étaient comptés dans un fichier est bien révolue.

<en-tete>: <instruction>

Cette structuration est utilisée aussi bien pour définir des boucles, des tests, des fonctions, des classes ou encore des méthodes.

Tests

Conditions booléennes En Python, tout ce qui n’est pas faux est vrai. Les conditions booléennes fausses se résument au données «vide» en plus du faux:

False, None, 0, "", [], list(), {}, dict(), (), tuple()

Expression booléennes Python propose les opérateurs de comparaison et les opérateurs booléen suivants:

<, <=, >, >=, !=, ==, is
and, or, not

L’utilisation du == permet de comparer l’équivalence de valeurs (donc d’objets), alors que is permet de comparer si deux variables référence une même instance.

>>> l1 = [1, 2, 3]
>>> l2 = [1, 2, 3]
>>> l1 == l2
True
>>> l1 is l2
False
>>> l3 = l1
>>> l3 is l1
True

Python offre une seule construction pour réaliser des tests: le if then else. Une particularité de cette mise en oeuvre est la possibilité d’enchaîner les tests avec la construction elif. Le else et le elif sont bien sûr optionnels. Enfin, a clause elif peut être utilisée plusieurs fois dans un même bloc.

if x == 'hello':
    print 'hello too!'
elif x == 'bonjour':
    print 'bonjour aussi!'
else:
    print 'moi pas comprendre'

Depuis la version 2.5, il est aussi possible d’utiliser une expression conditionnelle (par opposition à la construction if du paragraphe précédent)

oeufs = 4 if souhaite_une_tarte() else 0

L’absence du switch se justifie par le fait que Python est un langage objet. Dans cette approche, l’utilisation du polymorphisme replace naturellement le switch. Lorsque l’on ne développe pas avec une approche objet, le switch se remplace simplement par l’utilisation d’un dictionnaire. Ce dictionnaire contient, par exemple, des fonctions associées à des clés (pas nécessairement des chaînes) pour choisir la bonne fonction en fonction du contexte

traitements = {
  'en': traiter_anglais,
  'de': traiter_allemand,
  'fr': traiter_francais,
  # etc.
}

def traiter_commande(langue, commande):
  traiter = traitements[langue]
  traiter(commande)

Les comparaisons entre chaînes de caractères se fait selon l’ordre lexicographique. Les comparaisons entre listes et tuples se fait éléments par éléments de la gauche vers la droite.

>>> 'abc' < 'abd'
True
>>> 'abc' < 'abcd'
True
>>> (1, 2, 3) < (1, 2, 4)
True
>>> (1, 2, 3) < (1, 2, 3, 4)
True
>>> (1, 2, 3, 4) < (1, 2, 4)
True

Boucles

Deux types de boucles sont disponibles: les boucles énumérées (for) et les boucles basées sur un test de fin (while). Ces deux constructions suivent le même schéma: un en-tête qui décrit l’évolution de la boucle, un ensemble d’instructions qui sont évaluées à chaque tour de boucle et une partie optionnel qui est évaluée en sortie de boucle (introduite par le mot-clé else). Enfin, comme en C les boucles peuvent contenir les branchements continue pour passer à l’itération suivante et break pour sortir de la boucle (dans ce cas la clause else n’est pas évaluée).

Boucles énumérées

Une boucle for définit une variable qui prend successivement toutes les valeurs de la séquence (liste ou tuple) parcourue (ligne 1). La clause else est évalué lorsque la séquence est épuisée et s’il n’y a pas eu de sortie de boucle avec un break (ligne 3).

for <var> in <sequence>:
    <instructions>
else:
    <instructions, sequence epuisee sans break>

La fonction range produit une liste de tous les entiers entre une borne inférieur et une borne supérieur. Cette construction est utile lorsqu’une boucle for repose sur l’utilisation d’une séquence d’entiers. La fonction range est utilisable de trois manières:

  • un seul paramètre spécifiant le nombre d’éléments (ligne 1),
  • deux paramètres spécifiant la borne inférieure (inclue) et supérieure (exclue) (ligne 3),
  • trois paramètres spécifiant les bornes et le saut (incrément entre deux éléments de la séquence) (ligne 5).
>>> range(6)
[0, 1, 2, 3, 4, 5]
>>> range(3, 7)
[3, 4, 5, 6]
>>> range(0, 10, 3)
[0, 3, 6, 9]

Boucle for parcourant une séquence de chaîne.

>>> a = ['hello', 'world']
>>> for elt in a:
...     print elt
...
hello
world

Boucle parcourant une liste de valeurs pour accéder aux éléments d’une liste.

>>> for idx in range(len(a)):
...     print idx, a[idx]
...
0 hello
1 world

S’il est nécessaire de parcourir une séquence tout en connaissant l’index de l’élément courant, la fonction enumerate est une solution plus «pythonesque».

>>> for idx, val in enumerate(a):
...     print idx, val
...
0 hello
1 world

Enfin, une chaîne étant une séquence de lettre, elle peut être parcourue comme une séquence.

>>> for lettre in 'bar':
...     print lettre
...
b
a
r

Boucles avec condition

Une boucle while définit une condition booléenne avec les même règles que pour les tests (ligne 1). Tant que cette condition est respectée, les instruction du bloc associé au while sont évaluées. La clause else est évalué lorsque la condition est fausse et qu’il n’y a pas eu de sortie de boucle avec un break (ligne 3).

while <condition>:
    <instructions>
else:
    <instructions, condition fausse>

La boucle suivante représente la boucle minimale définissable avec un while. Certes, elle est stupide mais elle permet d’illustrer l’utilisation du Ctrl-C pour interrompre un traitement en cours d’exécution.

>>> while 1:
...     pass
...
KeyboardInterrupt

Fonctions

Il n’y a que des fonctions en Python (et non une distinction entre fonction et procédures comme dans certains langages). Une fonction est définie avec le mot clé def. Une fonction retourne toujours une valeur. Si une fonction ne contient pas de clause return, la valeur None est alors retournée.

>>> def fib(n): # suite de fibonacci jusque n
...     a, b = 0, 1
...     while b < n:
...         print b,
...         a, b = b, a + b
...
>>> fib(100)
1 1 2 3 5 8 13 21 34 55 89

Les paramètres d’une fonction peuvent être définis avec une valeur par défaut. A l’utilisation de la fonction, ces paramètres sont alors optionnels. Dans le cas d’une fonction avec des paramètres optionnels, l’utilisation des paramètres doit être ordonnée, les paramètres sont affectés dans l’ordre de leur définition, ou nommée.

>>> def welcome(name, greeting='Hello', mark='!'):
...     print greeting, name, mark
...
>>> welcome('world')
Hello world !
>>> welcome('monde', 'Bonjour')
Bonjour monde !
>>> welcome('world', mark='...')
Hello world ...

Python permet de définir des fonctions anonymes, en utilisant la forme lambda [5]. Une telle fonction est réduite à une simple expression. Cette construction est utile pour passer des fonctions en paramètre d’autre fonctions pour les configurer ou dans le cas des list mappings (voir section [subChap1List]). Nous verrons un peu plus tard que ce n’est pas la seule possibilité.

>>> def compare(a, b, func=(lambda x,y: x < y)):
...     return func(a,b)

>>> compare(1, 2)
True
>>> compare(1, 2, func=(lambda x,y: x > y))
False

Documenter

La documentation fait partie intégrante du code. En plus de la possibilité de mettre des commentaires avec l’utilisation de #, Python offre la possibilité d’écrire du code auto-documenté. Ceci est valable pour les fonctions, les classes et les modules. Une telle documentation d’un élément est accessible vie l’attribut __doc__ de ces différents éléments. Cette technique est utilisable automatiquement par les environnements intégrés de développement ou par l’utilisation de la fonction help en mode interactif.

La définition des commentaires avec # devrait être réservé aux remarques techniques sur le code et pour les développeur de l’élément en question. La documentation doit quant à elle être destinée au développeurs mais surtout aux utilisateurs de l’élément documenté. Elle doit donc refléter le comportement de l’élément. Cette seconde forme est à mettre en parallèle avec la javadoc pour les habitué(e)s.

La définition de la documentation se fait sur la première ligne après la déclaration en utilisant des chaînes de caractères multi-lignes, même si la documentation tient sur une seule, en utilisant des guillemets doubles (ligne 2-5). Une bonne manière de définir la documentation est de fournir une version courte sur une ligne, de passer une ligne et de fournir des détails.

>>> def dummy():
...     """Cette fonction est du pipeau...
...
...     Cette fonction est vraiment du pipeau,
...     elle ne fait absolument rien."""
...     pass

>>> help(dummy)
Help on function dummy in module __main__:

dummy()
        Cette fonction est du pipeau...

        Cette fonction est vraiment du pipeau,
        elle ne fait absolument rien.

Autres éléments sur les séquences

Fonctions de manipulation de séquences

Python offre un certain nombre de fonction permettant d’appliquer des traitements sur tous les éléments d’une séquence afin de définir des filtres, de réaliser des calculs ou de calculer des hashcode.

Filtre

La fonction filter applique la fonction passée en premier argument sur chacun des éléments de la séquence passée en second argument et retourne une nouvelle liste qui contient tous les éléments de la séquence pour lesquels la fonction a retourné une valeur vrai.

>>> def funct1(val):
...     return val > 0
...
>>> filter(funct1, [1, -2, 3, -4, 5])
[1, 3, 5]
>>> def iseven(x):
...     return x % 2
...
>>> filter(iseven, [1, 2, 3, 4, 5, 6])
[1, 3, 5]

Application

La fonction map applique la fonction passée en premier argument sur chacun des éléments de la ou des séquences passées en paramètre. Dans le cas où plusieurs séquences sont passées en paramètre, la fonction doit prendre autant de paramètres qu’il y a de séquences. map retourne une liste contenant le résultat de chacun des calculs.

>>> def sum(x, y):
...     return x + y
...
>>> map(sum,  [1, 2, 3], [4, 5, 6])
[5, 7, 9]
>>> map(iseven, [1, 2, 3, 4, 5, 6])
[1, 0, 1, 0, 1, 0]

Réduction

La fonction reduce réduit une séquence par l’application récursive d’une fonction sur chacun de ses éléments. La fonction passée comme premier paramètre doit prendre deux arguments. La fonction reduce prend un troisième paramètre optionnel qui est la valeur initiale du calcul récursif.

>>> reduce(sum, [1, 2, 3, 4, 5])
15
>>> reduce(sum, [1, 2, 3, 4, 5], -5)
10

Listes en compréhension

La définition de listes en compréhension [6] permet de créer des listes de manière concise sans utiliser aux fonctions map, filter, etc.

L’exemple suivant (ligne 2) permet d’appliquer une même opération à tous les éléments d’une liste. Cet exemple est donc similaire à l’utilisation de la fonction map. La suite (ligne 4) permet de ne sélectionner que certains éléments de la liste, ici les nombres impaires. Cet exemple est similaire à l’utilisation de la fonction filter. La ligne 6 montre une combinaison des deux formes, en élevant au carré les nombres pairs de la liste foo.

>>> foo = [1, 2, 3, 4]
>>> [elt * 2 for elt in foo]
[2, 4, 6, 8]
>>> [elt for elt in foo if elt % 2]
[1, 3]
>>> [elt**2 for elt in foo if elt % 2 is 0]
[4, 16]

Il est enfin à noter que l’utilisation de ces constructions est en général plus performante que l’utilisation d’une boucle for. En effet, leur mise en oeuvre est faite au coeur de l’interprète au lieu d’être interprétée.

Itérateurs et générateurs

Les itérateurs (PEP 234) et les générateurs (PEP 255) sont apparus avec les versions 2.2 et 2.3 de Python. Ils sont monnaie courante depuis Python 2.4, et leur usage est recommandé.

Itérateurs

L’apparition des itérateurs a modifié la philosophie des boucles for pour le parcours de séquences. Dans les version précédentes de Python, l’itération sur une séquence se faisait en utilisant l’index pour accéder aux éléments de la séquence. Lorsque la séquence était terminée, l’exception IndexError (voir section [sec:chap3:exceptions] pour les exceptions) signalait la fin de l’itération.

Désormais, un itérateur est un objet (voir le chapitre [chap3] pour les objets) auquel on demande l’élément suivant. Lorsqu’il n’y a plus de suivant, l’exception StopIteration signale la fin de l’itération. Les types de bases comme les listes, dictionnaires, tuples retournent désormais des itérateurs pour leur parcours. Ces itérateurs sont utilisés automatiquement par les boucles for et par l’opérateur in par exemple.

La définition d’un itérateur peut se faire avec la fonction iter des deux manières suivantes: soit l’argument est une séquence (ou fournit un itérateur), soit le paramètre est une fonction qui sera appelée tant que la sentinelle n’est pas retournée par la fonction. L’exemple suivant présente la première formes.

>>> iter([1,2,3,4])
<listiterator object at 0x...>

Générateurs

Les générateurs peuvent être considérés comme une évolution des listes en compréhension. Syntaxiquement, les [] ou list() deviennent des (), comme le montre l’exemple suivant. Une des différences entre les deux formes concerne l’utilisation de la mémoire: un générateur ne construit pas toute la séquence en mémoire a priori, mais chaque élément de la séquence est produit lorsque c’est nécessaire. Pour obtenir une séquence construite par un générateur, il suffit de passer le générateur en paramètre de la construction d’une liste [7].

>>> (elt * 2 for elt in foo)
<generator object <genexpr> at 0x...>

>>> for i in (elt * 2 for elt in foo):
...     print i,
2 4 6 8
>>> list(elt * 2 for elt in foo)
[2, 4, 6, 8]

La définition de fonctions générateurs se fait en utilisant le mot clé yield. Le déroulement de la fonction est interrompu à chaque utilisation de yield, le reste sera évalué lorsque l’appelant demandera le prochain élément. Le yield peut être considéré comme un return qui ne met pas fin à l’exécution de la fonction.

Lorsqu’il est appelé, un générateur retourne un itérateur, ce qui permet une utilisation dans une boucle for comme dans l’exemple suivant. C’est l’utilisation de l’itérateur retourné qui pilote le déroulement du générateur: l’exécution s’arrête sur le yield qui est évalué à chaque appel du suivant sur l’itérateur.

>>> def generateur():
...     i = 0
...     while i < 10:
...             yield i
...             i += 1
...
>>> for i in generateur():
...     print i,
...
0 1 2 3 4 5 6 7 8 9

Boucles énumérées problématiques

Certaines boucles de manipulation des listes peuvent être problématiques, par exemple si la boucle modifie la liste qu’elle est en train de parcourir. Pour que ce genre de boucle se passe bien, et ne soit pas sans fin, il est important dans certains cas de faire une copie de la liste pour le parcours et de travailler sur la version originale de la liste. Par défaut, tout argument est passé par référence et donc sans copie (en fait copie de la liste des références et non des objets contenus) la boucle suivante serait sans fin: Cette boucle rajoute les éléments positifs en début de liste, comme le premier élément est positif cette boucle est toujours sur l’élément 1 alors que la liste ne fait que croître.

>>> a = [1, -2, 3, -4, 5, -6]
>>> for elt in a [:]:
...     if elt > 0: a.insert(0, elt)
...
>>> a
[5, 3, 1, 1, -2, 3, -4, 5, -6]

Exercices

Manipulations de données

Le fichier listetu.py contient une liste de 10 dictionnaires. Etudier cette structure de données et définir une fonction qui écrit chaque fiche sur la sortie standard de la manière suivante (en sautant une ligne entre chaque fiche) :

dossier    : 1
nom        : doe
prenom     : john
universite : lille1
discipline : informatique
niveau     : 4
moyenne    : 17
[1]Dans tout ce support l’invite du shell sera identifié par la lettre $.
[2]En Python, ce calcul se résume à la possibilité pour l’objet de recevoir un message particulier.
[3]Il est toutefois à noter que cette facilité ne devrait être utilisées que sous couvert du polymorphisme, sans quoi la lisibilité du programme s’en trouve réduite.
[4]Lorsque la dernière référence à un objet est détruite alors l’objet et lui même effectivement détruit.
[5]Ici encore, cela sent la programmation fonctionnelle type Lisp à plein nez...
[6]Compréhension: Ensemble des caractères qui appartiennent à un concept et servent à le définir. Définir un ensemble en compréhension. Par opposition à en extension.
[7]Depuis Python 2.4, la notation des listes en compréhension revient à la construction d’une liste à partir d’un générateur.