~ / howtos /

HOWTO Python

Copyright © 2006—2007, Renaud Blanch.
last update : 22 novembre 2007

Python ?

L'esprit

Python est un langage de programmation simple et puissant. Comme il est interprété et bien adapté à la réalisation rapide de petits programmes, on peut le qualifier de "langage de script". L'interpréteur se lance en tapant python dans une console, il affiche alors un message de bienvenue et attend les entrées de l'utilisateur en lui proposant une invite (>>> ou ... si la ligne précédente n'est pas complète). Une fois l'entrée complète, elle est évaluée, puis son résultat est affiché.

% python
Python 2.5 (r25:51918, Sep 19 2006, 08:49:13) 
[GCC 4.0.1 (Apple Computer, Inc. build 5341)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> for adjective in ["brave", "new", "cruel"]:
...     print "Hello, %s world!" % adjective
... 
Hello, brave world!
Hello, new world!
Hello, cruel world!

Sa syntaxe est minimaliste, elle permet une programmation impérative et orientée objet. Le design du langage a été guidé depuis son origine par une "philosophie" qui est résumée dans le "Zen of Python", accessible en tapant import this depuis l'invite interactive de Python :

>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

Python est cependant plus qu'un langage. Il est distribué avec un ensemble de bibliothèques standard qui permettent de traiter la plupart des tâches classiques, et ce de manière portable. De la gestion de documents structurés (HTML, XML) à l'accès aux bases de données, de la création d'interface graphiques à la gestion des protocoles réseau, un grand nombre de services sont proposés avec le langage. Par ailleurs, un grand nombre de bibliothèques plus spécialisées sont proposées par une communauté très active autour du langage.

Distributions

Le site de téléchargement de Python propose des versions binaires de Python pour Windows et Mac OS X ainsi que les sources qui permettent de compiler Python sur pratiquement tous les systèmes existants. La société ActiveState propose une distribution du langage baptisée ActivePython proposée en version binaire pour de nombreux systèmes POSIX (AIX, HP-UX, Linux, Solaris).

Exécution

Une fois Python installé, taper python dans une console ou lancer l'exécutable correspondant démarre l'interpréteur et vous donne la main. C'est le mode idéal pour découvrir Python et tester des idées. Il est aussi possible d'écrire son code dans un fichier texte, dénommé traditionnellement avec l'extension .py, et de l'exécuter en mode non-interactif en passant le nom du programme à l'interpréteur :

% echo 'print "Hello world!"' > test.py
% python test.py
Hello world!

Premiers pas

Les commentaires commencent au caractère # et se prolongent jusqu'à la fin de la ligne.

>>> 5*6 # calcul du produit de 5 et de 6
30

Types primitifs

Python sait manipuler entiers, complexes et flottants, les entiers n'étant pas limités en précision.

>>> 1234567 ** 89
139574185988226216352411677967079384654819840770263154148901015711434587555916221871855053939370772085656915237689603767083349991793713346471091572628494106076146419389211087459209170823939332677859093756734470172351895079430772488109105692648444480028380122417140932569432824316430008740958025053716735421344729410520681559715479472541631321425013768978692403188995380725532383634472359562908542469547971146457062422356845701571795811721146126639734809904577967012608961528997012178537266420111446716001493476975934877110973383995456863730247L
>>> 1j*1j
(-1+0j)

Il existe en Python un objet "rien" (None), qui peut être utilisé pour donner une valeur nulle à une variable.

>>> p = None
>>> p          # aucune valeur n'est retournée (et donc affichée)
>>>

Les booléens sont représentés par leur deux valeurs, True (vrai) et False (faux).

>>> 1 == 1
True
>>> True != False
True
>>> "ete" == "hiver"
False

Les chaînes de caractères peuvent-être délimitées par des apostrophes (') ou des guillemets (") simples ou triples (''' ou """). Les signes triples permettent d'inclure les sauts de ligne dans la chaîne de caractère. Les caractères spéciaux (signes de délimitation de chaine, sauts de ligne ...) sont échappés avec la barre oblique inversée (\).

>>> """toto
... et
... titi
... """
'toto\net\ntiti\n'
>>> "une chaine contenant l'apostrophe"
"une chaine contenant l'apostrophe"
>>> 'une chaine "avec des guillemets"'
'une chaine "avec des guillemets"'
>>> "\"" == '"'
True

Python permet de manipuler efficacement les chaînes de caractères.

>>> name = "Yvan-Chrysostome"
>>> name.lower()     # les chaînes de caractères sont des objets
'yvan-chrysostome'
>>> name             # la chaîne originale est intacte
'Yvan-Chrysostome'
>>> name.split("-")
['Yvan', 'Chrysostome']
>>> name.replace("Yv", "Je")
'Jean-Chrysostome'
>>> name.count("o")
2

Les chaînes se comportent comme des séquences de caractères, c'est-à-dire que l'ont peut accéder à des sous-séquences de la chaîne en utilisant une notation basée sur des indices :

>>> name[0]   # les indices commencent à 0
'Y'
>>> name[-1]  # les indices négatifs comptent à partir de la fin
'e'
>>> name[0:5] # on peut couper des tranches (la borne de droite est exclue)
'Yvan-'
>>> name[5:]  # si on ne spécifie pas une borne, on prend tout
'Chrysostome'

Types structurés

Python propose plusieurs types qui permettent de constituer des collections d'objets.

Tuples

Le plus simple est le tuple qui permet de regrouper plusieurs objets pour en former un nouveau :

>>> position = (45, 5)                 # groupage
>>> grenoble = ("Grenoble", position)  # groupage
>>> grenoble
('Grenoble', (45, 5))
>>> ville, (x, y) = grenoble           # dégroupage !
>>> x
45
>>> x, y = y, x                        # permutation !
>>> x, y
(5, 45)

Listes

Le listes permettent de stocker en séquences plusieurs objets.

>>> names = ["pim", "pam", "pom"]
>>> names[2]
'pom'
>>> names[2] = "poum"
>>> names
['pim', 'pam', 'poum']
>>> names.insert(0, "toto")
>>> names
['toto', 'pim', 'pam', 'poum']

Dictionnaires

Les dictionnaires sont des tableaux associatifs, ils permettent de stocker des objets et le les retrouver à partir de clés qui sont aussi des objets.

>>> months = {1:"janvier", 2:"fevrier", 3:"mars", 4:"avril", 5:"mai", 6:"juin", 7:"juillet", 8:"aout", 9:"septembre", 10:"octobre", 11:"novembre", 12:"decembre"}
>>> months[11]
'novembre'
>>> z = {}         # un dictionnaire vide
>>> z[0, 0] = 4    # les clés peuvent aussi être des tuples ...
>>> z[1, 0] = 7    # ... ce qui permet de créer des tableaux ...
>>> z[1, 1] = 12   # ... à plusieurs dimensions.
>>> z
{(1, 0): 7, (0, 0): 4, (1, 1): 12}
>>> i, j = 1, 1
>>> z[i, j]
12

Structures de contrôle

Contrôle du flot d'exécution

Les structures de contrôles de Python sont celles des langages impératifs classiques, la boucle étant habituellement utilisée pour parcourir des séquences. Les blocs d'instructions sont délimités par l'indentation.

>>> t = 12
>>> if t < 10:
...     temperature = "froid"
... elif t < 20:
...     temperature = "tiede"
... else:
...     temperature = "chaud"
... 
>>> temperature
'tiede'
>>> while t < 20:
...     t += 1
... 
>>> t
20
>>> found = False
>>> for name in ["pim", "pam", "poum", "toto"]:
...     if name == "toto":
...         found = True
... 
>>> found
True

Il est possible de passer certaines itérations d'une boucle ou de l'interrompre complètement.

>>> for name in ["toto", "pim", "pam", "poum"]:
...     if len(name) != 3:
...         continue
...     if "m" in name:
...         break
... 
>>> name
'pim'

Il est aussi possible de laisser certains blocs d'instruction vide grâce à une "non-opération" (pass) qui ne fait rien mais qui peut être nécessaire puisque les blocs d'instructions n'ont pas de délimiteurs.

>>> t = 12
>>> temperature = "tiede"
>>> if t < 10:
...     temperature = "froid"
... elif t < 20:
...     pass
... else:
...     temperature = "chaud"
... 
>>> temperature
'tiede'

Fonctions et procédures

Python permet bien sûr de définir des fonctions et des procédures. La seule différence entre les deux tient en ce qu'elles retournent ou non une valeur. Une procédure, sans return explicite donc, renvoie en fait None qui peut simplement être ignoré.

>>> def create_point(x, y, z):
...     return (x, y, z)
... 
>>> create_point(4, 6, 3)
(4, 6, 3)

On peut donner des valeurs par défaut aux arguments d'une fonction.

>>> def create_point(x=0, y=0, z=0):
...     return (x, y, z)
... 
>>> create_point(1, 2)
(1, 2, 0)

Le passage d'argument peut se faire en nommant explicitement les arguments auxquels on donne une valeur, ce qui permet de ne pas utiliser la valeur par défaut de l'un d'entre-eux tout en utilisant les autres.

>>> create_point(z=7, y=5)
(0, 5, 7)

Les fonctions peuvent être documentées en insérant des chaînes de caractères juste après leurs définitions. Par convention, on indique brièvement sur la première ligne du commentaire l'utilité de la fonction, et on développe éventuellement sa description après avoir passé une ligne. L'intérêt de cette documentation est qu'elle est accessible par la fonction help.

>>> def create_point(x=0, y=0, z=0):
...     """Create a 3D point."""
...     return (x, y, z)
... 
>>> def create_point(x=0, y=0, z=0):
...     """Create a 3D point.
... 
...     The function creates a point with its arguments:
...     x -- first coordinate
...     y -- second coordinate
...     z -- last coordinate
...     """
...     return (x, y, z)
... 
>>> help(create_point)

L'aide s'affiche dans un nouvel écran, il faut taper sur q pour en sortir (h pour avoir la liste des commandes accessibles dans l'aide).

Help on function create_point in module __main__:

create_point(x=0, y=0, z=0)
    Create a 3D point.
    
    The function creates a point with its arguments:
    x -- first coordinate
    y -- second coordinate
    z -- last coordinate
(END) 

Plus loin

Gestion des erreurs

Un des leitmotiv de Python est : "easier to ask forgiveness than permission" (EAFP) —il est plus facile de demander pardon que de demander la permission— par opposition à "look before you leap" (LBYL) —regardez avant de sauter. Concrètement, cela veut dire qu'il faut mieux tenter une opération qui peut échouer, quitte à adopter des mesures particulières en cas d'échec, plutôt que de vérifier à chaque appel toutes les conditions requises.

Ce style de programmation est possible car les exceptions font partie du langage et n'entraînent pratiquement aucun surcoût à l'exécution.

>>> def lbyl_ratio(x, y):
...     # programmation à la C avec test des préconditions.
...     if y == 0:                    # on traite d'abord
...         return x*float("Inf")     # les cas particuliers 
...     else:                         # puis le cas général
...         return x/float(y)
... 
>>> def eafp_ratio(x, y):
...     # programmation optimiste à la Python.
...     try:                          # on essaie
...         return x/float(y)         # le cas général en premier
...     except ZeroDivisionError:     # puis on traite
...         return x*float("Inf")     # les exceptions
... 
>>> lbyl_ratio(3, 0) == eafp_ratio(3, 0)
True

En Python, on se sert des exceptions comme mécanisme général de gestion des erreurs. Plutôt que de retourner un code d'erreur en cas de problème, on lance une exception qui sera éventuellement traitée par l'appelant.

>>> raise RuntimeError("message à caractère informatif")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: message à caractère informatif

Typage dynamique

En Python, les objets ont la particularité d'être typés dynamiquement. Cela veut dire que les informations de type ne sont pas liées aux variables mais à leur valeur. Cela signifie aussi qu'une variable ne peut pas être déclarée, mais qu'elle prend vie à partir du moment où on lui donne une valeur.

>>> type(a)        # quel est le type de a ?
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> a = [1, 3, 5]  # a est maintenant une liste
>>> type(a)
<type 'list'>
>>> len(a)         # len donne la longueur d'une séquence
3
>>> a = min        # la fonction min fait partie du langage
>>> type(a)
<type 'builtin_function_or_method'>
>>> a(12, 45)   # a est donc maintenant une fonction
12
>>> len(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: object of type 'builtin_function_or_method' has no len()

Des classes et des objets

Modèle objet

Python dispose d'un modèle objet qui permet d'associer des méthodes à des instances de classes. Comme il n'y a pas de déclaration en Python, les membres d'une instance ne peuvent exister que si elles sont initialisées et doivent donc être créées dans le constructeur de l'objet qui est une fonction membre particulière appelée __init__. Python se distingue des langages usuels supportant la programmation orientée objet par le passage explicite de l'instance aux méthodes de l'objet. Cette instance est le premier argument des méthodes, et il est nommé self par convention :

>>> class Rectangle:
...     def __init__(self, width, height):
...         # les attributs de l'objet sont initialisés
...         # avec les valeurs des arguments du constructeur
...         self.width = width
...         self.height = height
...     def area(self):                       # définition d'une méthode.
...         """Compute rectangle area."""     # calcul à l'aide des 
...         return self.width * self.height   # attributs de l'instance
... 
>>> r = Rectangle(10, 7) # création d'une instance
>>> r.area()             # self est passé automatiquement
70
>>> r.width = 23         # les attributs sont toujours publics
>>> r.area()
161

Héritage

Comme dans tout langage objet, on peut spécialiser des classes pour enrichir leur comportement. Le typage étant dynamique, toutes les méthodes sont virtuelles.

>>> class Shape:
...     """An abstract shape."""
...     def __init__(self, position):
...         self.position = position
...     def area(self):                 # méthode virtuelle pure réalisée
...         raise NotImplementedError   # en levant une exception
...         
>>> s = Shape((12, 34))         # la classe n'est pas vraiment abstraite, 
>>> s.position                  # on peut l'instancier et l'utiliser
(12, 34)
>>> s.area()                            # l'invocation d'une méthode
Traceback (most recent call last):      # virtuelle pure crée une erreur
  File "<stdin>", line 1, in <module>
  File "shape.py", line 7, in area
    raise NotImplementedError
NotImplementedError
>>>
>>> class Rectangle(Shape):                     # spécialisation
...     """A concrete rectangle inheriting from Shape."""
...     def __init__(self, position, width, height):
...         Shape.__init__(self, position)      # "super"-constructeur
...         self.width = width
...         self.height = height
...     def area(self):                         # réalisation
...         return self.width * self.height
...
>>> r = Rectangle((12, 34), 56, 78)
>>> r.area()
4368
>>> r.position
(12, 34)

Il est aussi possible de faire de l'héritage multiple précisant plusieurs classes de base dans la définition de la classe. Cependant, cette possibilité est très peu utilisée en Python car le typage dynamique permet d'exploiter le polymorphisme sans nécessairement utiliser l'héritage. Cette possibilité est dénommée "duck-typing" (cf. ci-dessous).

Duck typing

L'idée du duck typing est que “If it looks like a duck and quacks like a duck, it must be a duck”, («si ça ressemble à un canard et que ça fait le bruit d'un canard, c'est sans doute un canard»). Le principe est qu'il suffit qu'un objet implémente les méthodes nécessaires à la réalisation d'une tâche (une interface au sens de Java) pour pouvoir les appeler. Il n'a pas besoin de dériver d'une classe de base qui matérialise cette interface. Concrètement, en programmation à la C++ ou à la Java, on ferait :

class Shape:             # classe "abstraite" spécifiant une interface
    def area(self):
        raise NotImplementedError
		
class Rectangle(Shape):
    def area(self):
        return "rectangle area: width * height"
		
class Circle(Shape):
    def area(self):
        return "circel area: radius * radius * pi"

Alors qu'en Python on peut simplement faire :

>>> class Rectangle:
...     def area(self): return "rectangle area"
... 
>>> class Circle:
...     def area(self): return "cirle area"
... 
>>> for shape in [Rectangle(), Circle()]:
...     print shape.area()                 # appel polymorphe
... 
rectangle area
cirle area

Gestion de la mémoire

Comme en Java, et contrairement au C++, c'est Python qui gère la mémoire à l'aide d'un "ramasse-miette" (garbage collector). Les objets sont détruits quand plus aucune référence ne permet d'y accéder. Aucune garantie n'est donnée quant au temps après lequel un objet inatteignable est détruit. Lors de la destruction de l'objet, une méthode de finalisation est appelée pour permettre de libérer les ressources. Cette méthode joue le même rôle qu'un destructeur mais le fait qu'on ne peut prévoir le moment où elle sera appelée interdit de s'en servir pour gérer des ressources automatiquement comme on le ferait en C++ (idiome du RAII).

>>> class Test:
...     def __del__(self): 
...         """Test finalizer."""
...         print "I'm dying!"
... 
>>> t1 = Test()  # une instance de Test est crée
>>> t2 = t1      # t2 permet aussi d'accéder à cette instance
>>> t1 = None    # t1 ne permet plus d'accéder à l'instance
>>> t2 = None    # plus aucune variable ne référence l'instance,
I'm dying!       # elle est susceptible d'être détruite ce qui
>>>              # arrive instantanément ici mais n'est pas garanti

Modules

Modulariser le code

Il est possible de séparer son code en différents fichiers. Cette séparation n'est pas forcée par la langage (contrairement à Java) mais est une facilité offerte qu'il ne faut pas négliger. On peut par exemple regrouper des fonctions, ou un ensemble de classes, liées entre-elles dans un unique fichier qui peut alors être utilisé comme un module. Le fichier euro.py donné ci-dessous offre par exemple un ensemble de fonctions liée à la conversion d'euros en francs :

"""Convert euros and francs.

This module isn't very useful for young students 
but can help old teachers.
"""

EURO = 6.55957


def francs2euros(f):
    """Converts francs to euros."""
    return f/EURO

def euros2francs(e):
    """Converts euros to francs."""
    return e*EURO
euro.py

Le module peut être chargé à l'aide de la commande import. Ses fonctions sont alors accessibles à l'intérieur de l'espace de nom créé pour le module :

>>> import euro  # charge le fichier euro.py dans l'espace de nom euro
>>> euro.euros2francs(12) # appel d'une fonction du module
78.714839999999995
>>> help(euro)            # la documentation du module
Help on module euro:

NAME
    euro - Convert euros an francs.

FILE
    /Users/blanch/writings/howtos/python/euro.py

DESCRIPTION
    This module isn't very useful for young students 
    but can help old teachers.

FUNCTIONS
    euros2francs(e)
        Converts euros to francs.
    
    francs2euros(f)
        Converts francs to euros.

DATA
    EURO = 6.5595699999999999

(END) 

Programme principal

Lorsqu'un script Python est passé comme paramètre à l'interpréteur, celui-ci exécute toutes les instructions qu'il contient. La manière conventionnelle de définir la fonction principale (main) d'un programme est la suivante :

import sys # importation de la bibliothèque standard système

...

def main(argv=None):
    if argv == None:
        argv = sys.argv

    # traitement des arguments de la ligne de commande
    # et exécution du programme
    ...    
    return 0

if __name__ == "__main__":
    sys.exit(main())

Le module getopt permet de traiter les arguments passés par la ligne de commande de manière générique. La structure typique de la fonction principale devient alors :

import sys

def process(arg, output, verbose):
    if verbose:
        print "processing %s" % arg
    pass

def usage(name):
    from textwrap import dedent
    usage = """\
    Usage: %s -[hov] <args>
      -h --help              show help
      -o --output <filename> change output
    """
    sys.stderr.write(dedent(usage % name))


def main(argv=None):
    if argv == None:
        argv = sys.argv

    name = argv[0]

    # extraction des options et de leur valeurs
    import getopt
    try:
        options, args = getopt.getopt(argv[1:], "ho:v", 
                                      ["help", "output=", "verbose"])
    except getopt.GetoptError, e:
        sys.stderr.write("%s\n" % e)
        sys.stderr.write("use -h for help\n")
        sys.exit(2)

    # valeurs par default
    output = sys.stdout
    verbose = False

    # traitement des options
    for option, value in options:
        if option in ["-h", "--help"]:
            usage(name)
            sys.exit(0)
        elif option in ["-o", "--output"]:
            output = file(value, "w")
        elif option in ["-v", "--verbose"]:
            verbose = True

    # traitement des arguments restants
    for arg in args:
        process(arg, output, verbose)

    return 0

if __name__ == "__main__":
    sys.exit(main())
main.py