Python

Introduction

Python est un langage de programmation, sous licence libre, orienté objet, multi-paradigme et multiplateformes.

Python est à la fois à typage dynamique (il n’utilise pas de déclaration de type explicite) et fortement typé (une fois qu’une variable a un type, cela a une importance).

Premier programme

Les programmes Python sont des scripts donc des fichiers textes.

Les blocs de code (fonctions, instructions if, boucles for ou while etc.) sont définis par leur indentation. L’indentation démarre le bloc et la désindendation le termine. Il n’y a pas d’accolades, de crochets ou de mots clés spécifiques.

Python utilise le retour chariot pour séparer les instructions, deux points (:) et l’indentation pour séparer les blocs de code.

#!/usr/bin/python
# coding: utf-8

# un programme python

print("Quelle est votre langue ? ")
langue = raw_input()
if langue == "fr" :
    message = "Bonjour le monde"
else :
    message = "Hello world"

# voir aussi : if ... elif ... else

print("Donnez un nombre : ")
nb = input()
i = 0

while i < nb:
    #print message
    print message, i + 1, " fois"
    i += 1

Dans un script Python :

  • le chemin de l’interpréteur avec lequel il doit être exécuté précédé des caractères #! (le shebang) est indiqué sur la première ligne.
  • on peut déclarer l’encodage utilisé (par défaut utf-8 en python 3) : # coding: utf-8
  • un commentaire commence par le caractère et finit à la fin de la ligne.

Il existe plusieurs manières d’exécuter un script Python :

  • le rendre exécutable : $ chmod +x ex-helloworld.py ; ./ex-helloworld.py
  • utiliser l’interpréteur Python : $ python ex-helloworld.py ou $ python3 ex-helloworld.py

Documentations

La documentation du langage Python :

Vous pouvez aussi obtenir facilement des informations sur tout objet, module ou fonction en utilisant l’interpréteur Python avec les fonctions dir(object), help(object) ou encore les docstrings avec l’attribut __doc__.

Par exemple pour le type str :

# code pour obtenir des informations sur les string :

# dir([object]) retourne la liste des attributs de l'objet object :
print dir(str) 

# help([object]) invoque le système d'aide intégré :
help(str) 

# visualiser les docstrings en appelant le paramètre __doc__ sur un objet :
print str.__doc__

Types de base

Les types de base en Python sont relativement complets et puissants, il y a entre autres :

  • Les objets numériques :
    • int est un entier illimité. Avant la version 3.0, ce type était dénommé long, et le type int correspondait à un entier 32 ou 64 bits. Néanmoins, une conversion automatique évitait tout débordement.
    • float est un flottant équivalent au type double du C
    • complex est une approximation d’un nombre complexe (typiquement deux float).
  • Les objets « itérables » : Les objets tuple sont des listes non modifiables d’objets hétérogènes. Les objets list sont des tableaux dynamiques (ils étendent automatiquement leur taille lorsque nécessaire) et acceptent des types de données hétérogènes. Les objets set sont des ensembles non ordonnés d’objets. Les objets frozenset forment une variante non modifiable des set. Les objets dict sont des tableaux associatifs (ou dictionnaires) permettant d’associer un objet (une clé) à un autre (une valeur). Les objets str sont des chaînes de caractères. Les chaines d’octets sont des objets bytes. Les objets str et bytes sont non modifiables. Les objets bytearray sont des chaînes d’octets modifiables.

En Python, on distingue deux types d’objets : les mutables (listes, dictionnaires, sets, …) et les non mutables (str, int, float, tuples, …).

Les mutables sont ceux qu’on peut modifier après leur création. Les non mutables (immuable) sont ceux qu’on ne peut pas modifier après création.

Les objets itérables sont parcourus à l’aide d’une boucle for de la manière suivante :

for element in objet_iterable:
    traiter(element)

Il est possible de dériver les classes des types de base pour créer ses propres types.

Connaître et modifier un type

En Python, les variables ne sont jamais explicitement typées. En se basant sur la valeur que vous lui assignez, Python gère les types de données en interne.

Pour connaître le type d’une variable, il suffit d’utiliser la fonction interne à Python type(). Pour transtyper (cast) une variable, on préfixe les parenthèses avec le type désiré.

#!/usr/bin/python

# Python 2

t = "Hello"
i = 0
nb = raw_input()

# transtypage :
a = int(nb)

# connaître le type d'une variable :
print type(t) # <type 'str'>
print type(i) # <type 'int'>
print type(nb) # <type 'str'>
print type(a) # <type 'int'>

# affichage
print "i = %d" % i # i = 0

Saisie au clavier

On utilisera suivant les versions de Python :

  • En Python 3, input() retourne une chaîne comme raw_input() en Python 2
  • En Python 2, input() est équivalent à eval(raw_input())

input(prompt) et raw_input(prompt) peuvent recevoir en argument une invite (prompt).

Pour lire plusieurs choses sur la même ligne :

print "Saisir un pays et une ville : "
mots = raw_input().split(" ")
pays = mots[0]
ville = mots[1]
print("Vous habitez à {} ({})".format(ville, pays))

print "Saisir un pays et une ville : "
pays, ville = raw_input().split(" ")
print("Vous habitez à {} ({})".format(ville, pays))

print "Saisir une longueur et une largeur : "
mots = raw_input("-> ").split(" ")
longueur = int(mots[0])
largeur = int(mots[1])
print(longueur * largeur)

print "Saisir une longueur et une largeur : "
longueur, largeur = map(int, raw_input("-> ").split(" "))
print(longueur * largeur)

Affichage à l’écran

On utilisera print :

# coding: utf-8

# affiche une chaîne de caractères
print("Hello world !")
s = "Hello world !"
print(s)
print(s + s) # concaténation

# affiche des valeurs
print(1)
i = 1
print(i)
f = 0.5
print('i = %d et f = %.1f' % (i, f))

# avec un formatage :
print('{0:5d}'.format(i))
print(repr(i).rjust(5))
print(repr(i).zfill(5))

# En Python3 :
print('.', end='') # pour désactiver le saut de ligne par défaut de print

Ou write du module sys :

import sys
sys.stdout.write('.') # il n'y aura pas de saut de ligne automatique ici

Tout est objet

Ceci est important et il ne fait aucun mal de le souligner : en Python tout est objet. Les chaînes sont des objets. Les listes sont des objets. Les fonctions sont des objets. Même les modules sont des objets.

Appeler des méthodes d’une classe :

# coding: utf-8

print "Saisir un message : "
message = raw_input()

print message.upper()
print message.lower()
print len(message)

# attention les chaînes ne sont pas modifiables même avec l'opérateur []
# message[0] = 'X
# il faut créer une nouvelle chaîne
message = 'X' + message[1:]
print message

Voir aussi : find(), replace(), split(), …

Les objets itérables

Listes simples

Les listes sont de simples suites d’éléments indexés. Dans les listes, les éléments qui se suivent ne sont pas nécessairement de même type.

Il existe deux façons de déclarer une liste :

# coding: utf-8
# Déclaration de listes
# soit en créant une liste d'emblée, vide ou non :

liste1 = []
liste2 = [1,2,3,4,5]
print type(liste1) # <type 'list'>
print type(liste2) # <type 'list'>

# soit en typant la variable :
liste3 = list()
print type(liste3) # <type 'list'>

# Utilisation des listes
print "Longueur de la liste liste2 : "
print len(liste2) # 5

print "Les éléments de la liste liste2 : "
for element in liste2:
    print(element)

print "Quelques éléments de la liste liste2 : "
for n in range(0,5,2):
     print liste2[n]

# modification d'un élément
liste2[0] = 0
print "Les éléments de la liste liste2 : "
print liste2 # [0, 2, 3, 4, 5]

# suppression d'éléments
del liste2[1]
liste2.remove(3)
print "Les éléments restants de la liste liste2 : "
print liste2 # [0, 4, 5]

liste3.append(1);
liste3.append("un");
print "Les éléments de la liste liste3 : "
print liste3 # [1, 'un']

liste1.append(3);
liste1.append(2);
liste1.append(1);
print "Les éléments de la liste liste1 : "
print liste1 # [3, 2, 1]

liste1.sort() # voir aussi : reverse()
print "Les éléments triés de la liste liste1 : "
print liste1 # [1, 2, 3]

Voir aussi : count(), index(), …

Les liste possèdent des méthodes leur permettant de se comporter comme des piles :

pile = [0,1,2,3,4,5]

pile.append(6);

print "Longueur de la pile : "
print len(pile) # 7

print "Élément de la pile : "
print pile.pop() # 6

print "Longueur de la pile : "
print len(pile) # 6

# ...

Tuples

Un tuple est une liste qui ne peut plus être modifiée.

# coding: utf-8
# Déclaration des tuples
# soit en créant une tuple d'emblée, vide ou non :

tuple1 = ()
tuple2 = (1,2,3,4,5)
print type(tuple1) # <type 'tuple'>
print type(tuple2)

# soit en typant la variable :
tuple3 = tuple()
tuple3 = (1,) # ne pas oublier la virgule
tuple3 = 1,2,3 # les parenthèses ne sont pas obligatoires

print type(tuple3)
print(tuple3)

# Utilisation des tuples
print "Longueur de tuple2 : "
print len(tuple2) # 5

print "Les éléments de tuple2 : "
for element in tuple2:
    print(element)

print "Quelques éléments de tuple2 : "
for n in range(0,5,2):
     print tuple2[n]

Les tuples et Python permettent de réaliser des assignations multiples :

x, y = (1, 2)
print x, y # 1 2

x, y = y, x
print x, y # 2 1

Dictionnaires

Structures de données fondamentales en Python, les objets dict sont des tableaux associatifs (ou dictionnaires) permettant d’associer un objet (une clef) à un autre (une valeur), le tout embrassé par des accolades { }. La clé est la valeur sont associées par un “:” et les membres se suivent, séparés par des virgules comme dans des listes.

L’utilisation d’un dictionnaire est en particulier utile lorsque les clés sont des mots qui permettent ainsi d’avoir une approche sémantique des données.

# coding: utf-8
# Déclaration des dictionnaires

dictionnaire1 = {}
dictionnaire2 = {'nom':'Descartes','prenom':'René'}
dictionnaire3 = dict()

print type(dictionnaire1) # <type 'dict'>
print type(dictionnaire2)
print type(dictionnaire3)

print "Les clés : "
for cle in dictionnaire2.keys():
    print cle

print "Les valeurs : "
for valeur in dictionnaire2.values():
    print valeur

print "Les clés/valeurs : "
for cle,valeur in dictionnaire2.items():
    print cle, valeur

# ajout
dictionnaire1['nom'] = 'Pascal'
dictionnaire1['prenom'] = 'Blaise'

print "Les clés/valeurs : "
for cle,valeur in dictionnaire1.items():
    print cle, valeur

dictionnaire1.clear()
print "Les clés/valeurs : "
for cle,valeur in dictionnaire1.items():
    print cle, valeur

Voir aussi : get(), has_key(), …

D’autres objets collections

En plus des collections précédentes (tuple, list, set, dict, bytearray, …), Python propose un module collections avec d’autres objets conteneurs.

Documentations :

  • Python2 : namedtuple(), deque, Counter, OrderedDict, defaultdict
  • Python3 : namedtuple(), deque, Counter, OrderedDict, defaultdict, ChainMap, UserDict, UserList et UserString

Exemple :

import collections

d1 = collections.OrderedDict()

# ou pour OrderedDict par exemple :
from collections import OrderedDict

d2 = OrderedDict()
  • OrderedDict : des dictionnaires qui conservent l’ordre d’insertion
import collections
#from collections import OrderedDict

d = collections.OrderedDict()
d['c'] = 1
d['b'] = 2
d['a'] = 3
# conserve l'ordre d'insertions
print(d.keys()) # ['c', 'b', 'a']
  • Counter : un dictionnaire spécialisé pour le comptage
import collections
#from collections import Counter
c = collections.Counter()
print(c['roger']) # 0
c['roger'] += 1
print(c['roger']) # 1
for i in range(0,5):
    c['robert'] += 1
    print(c['robert']) # 0 1 2 3 4 5

print(c) # Counter({'robert': 5, 'roger': 1})

# compte les occurences
print(collections.Counter('le soleil brille')) # Counter({'l': 5, 'e': 3, ' ': 2, 'i': 2, 'b': 1, 'o': 1, 's': 1, 'r': 1})
  • namedtuple : des tuples nommés et structurés
#from collections import namedtuple
Fiche = collections.namedtuple("Fiche", "nom prenom age")
f = Fiche(nom="Dupond", prenom="robert", age=66)
print(f) # Fiche(nom='Dupond', prenom='robert', age=66)
# toujours itérable :
for c in f:
    print c # Dupond robert 66

print(f.nom) # Dupond
  • deque : des listes à double entrée, les objets de la liste peuvent ainsi être ajoutés ou retirés, soit à gauche soit à droite, et en temps constant
#from collections import deque
d = collections.deque('ell')
for elem in d:
    print(elem.upper()) # E L L

d.append('o') 
d.appendleft('h')
print(d) # deque(['h', 'e', 'l', 'l', 'o'])

d.pop()
print(d) # deque(['h', 'e', 'l', 'l'])
print(collections.deque(reversed(d))) # deque(['l', 'l', 'e', 'h'])

Ramasse-miettes

Si créer des instances est simple, les détruire est encore plus simple. En général, il n’y a pas besoin de libérer explicitement les instances, elles sont libérées automatiquement lorsque les variables auxquelles elles sont assignées sont hors de portée. Sinon, le mot-clé del sert à supprimer explicitement une instance.

La technique du ramasse-miettes (garbage collector) Python est le « comptage de références » et lorsque ce compteur descend alors à 0, Python détruit l’instance automatiquement. En général, vous pouvez simplement ignorer la gestion mémoire et laisser Python nettoyer derrière vous car les fuites mémoire sont rares avec ce langage.

Les fonctions

Python permet la programmation procédurale.

Déclaration de fonctions

Python dispose de fonctions comme la plupart des autre langages, mais il n’a pas de fichiers d’en-tête séparés comme C++.

Lorsque vous avez besoin d’une fonction, vous n’avez qu’à la déclarer et l’écrire.

def foo(n):

Le mot clé def débute une déclaration de fonction, suivi du nom de la fonction, puis des arguments entre parenthèses. Les arguments multiples (non montré ici) sont séparés par des virgules.

Les fonctions Python ne définissent pas le type de leur valeur de retour, elle ne spécifient même pas si elle retournent une valeur ou pas. En fait chaque fonction Python retournera une valeur, si la fonction exécute une instruction return, elle va en retourner la valeur, sinon elle retournera None, la valeur nulle en Python.

Les arguments ne spécifient pas de types de données. En Python, les variables ne sont jamais explicitement typées. En se basant sur la valeur que vous lui assignez, Python gère les types de données en interne.

Documentation des fonctions

Vous pouvez documenter une fonction Python en lui donnant une chaîne de documentation (doc string).

def foo(n):
    """Je suis une fonction qui reçoit un paramètre n.

    Je retourne un string."""

print foo.__doc__

Les tripes guillemets indiquent une chaîne multi-lignes.

Une doc string, si elle existe, doit être la première chose déclarée dans une fonction (la première chose après les deux points). Techniquement parlant, vous n’êtes pas obligés de donner une doc string à votre fonction, mais vous devriez toujours le faire.

Tout est objet, même les fonctions

Une fonction, comme tout le reste en Python, est un objet.

Qu’est-ce qu’un objet ? Chaque langage de programmation définit le terme «objet» à sa manière. En Python, tout est objet dans le sens où tout peut être assigné à une variable ou passé comme argument à une fonction.

Exemples

Les fonctions Python n’ont pas de begin ou end explicites, ni d’accolades qui pourraient marquer là ou commence et ou se termine le code de la fonction. Le seul délimiteur est les deux points (:) et l’indentation du code lui-même.

#!/usr/bin/python
# coding: utf-8

def reponse(x): 
    """Affiche la réponse x"""
    print "La réponse est", x

t = "Hello"
i = 0

reponse(t) # La réponse est Hello
reponse(i) # La réponse est 0

def ratio(numerateur, denominateur=1): 
    """Retourne le ratio n/d"""
    return numerateur/denominateur

print ratio(1, 2) # 0
print ratio(1., 2.) # 0.5
print ratio(5) # 5

Remarque : Comme tout langage à typage dynamique, il n’est pas possible d’effectuer une surchage de fonctions (ou de méthodes). Voir : Comment effectuer une surcharge ?

Paramétres par défaut

Python autorise les paramétres par défaut pour les fonctions :

def foo(a=2):
    print a

foo()       # affiche 2
foo(12)     # affiche 12
foo(a=12)   # affiche 12
foo()       # affiche 2

def bar(a=[]):
    print a

bar()           # affiche []
bar([1,2,3,4])  # affiche [1, 2, 3, 4]
bar()           # affiche []

Attention, le paramètre par défaut est construit une fois pour toute quand la fonction est générée. Cela peut provoquer des effets de bord lorsque le paramètre est mutable :

import random

def stuff(a=[]):
    a.append(random.randint(0,9))
    return a

print stuff()       # affiche [0]
print stuff()       # affiche [0, 8]
print stuff()       # affiche [0, 8, 8]
print stuff()       # affiche [0, 8, 8, 7]
print stuff([])     # affiche [9]
print stuff([5,1])  # affiche [5, 1, 6]
print stuff()       # affiche [0, 8, 8, 7, 1]

Notions de référence

En fait, une variable est un nom (un label, un identifiant) pointant vers une référence d’un objet.

a = 1
a = 2

Attention, a n’est pas un emplacement mémoire qui stocke la valeur 1 puis la valeur 2. En Python, a est une référence à un objet avec la valeur 1, puis est réaffecté en tant que référence à un objet avec la valeur 2 :

a = 2
print "Référence de a : %d" % id(a) # Référence de a : 33956160
a = 1
print "Référence de a : %d" % id(a) # Référence de a : 33956184
b = a
print "Référence de b : %d" % id(b) # Référence de b : 33956184
b = 3
print "Référence de b : %d" % id(b) # Référence de b : 33956136

Lorsqu’on a affecté a à b, on a copié la valeur de la référence de a dans b (à ce moment là, a et b ont la même référence). Ici, les valeurs 1, 2 et 3 sont des « objets » différents et les références ne sont donc pas les mêmes.

Idem pour une liste :

liste1 = [1, 2, 3]
print "Référence de liste1 : %d" % id(liste1) # Référence de liste1 : 140042289226384
liste2 = liste1
print "Référence de liste2 : %d" % id(liste2) # Référence de liste2 : 140042289226384
liste2.append(4)
print(liste2)                                 # [1, 2, 3, 4]
print(liste1)                                 # [1, 2, 3, 4]
print "Référence de liste1 : %d" % id(liste1) # Référence de liste1 : 140042289226384
print "Référence de liste2 : %d" % id(liste2) # Référence de liste2 : 140042289226384
print liste1 is liste2                        # True

Mais ici, liste1 et liste2 ont la même référence car il n’y a qu’un seul objet (mais deux identifiants liste1 et liste2 qui ont la même valeur de référence).

C’est ce mécanisme qui est utilisé quand on passe des arguments à une fonction : Il y un passage de références (par valeur) des paramètres de la fonction. C’est la valeur de la référence qui est copiée.

def ajouter(l, a):
    print "Référence de liste : %d" % id(liste) # Référence de liste : 140660673366712
    l.append(a)

liste = [1,2,3,4]
print "Référence de liste : %d" % id(liste)     # Référence de liste : 140660673366712
print liste                                     # [1, 2, 3, 4]
ajouter(liste,5)
print liste                                     # [1, 2, 3, 4, 5]
print "Référence de liste : %d" % id(liste)     # Référence de liste : 140660673366712

Évidemment, cela dépendra si l’objet passé en argument est mutable ou non.

Gestion des exceptions

Python permet la gestion des exceptions afin de faciliter la mise en oeuvre de code robuste.

Lire : les exceptions en Python2 ou en Python3.

Une exception est l’interruption de l’exécution du programme à la suite d’un événement particulier (c’est-à-dire exceptionnel !) et le transfert du contrôle à des fonctions spéciales appelées gestionnaires.

def ratio(num, den):
    return num/den

print ratio(1., 2.) # affiche 0.5

# le programme s'arrête et lève une exception :
print ratio(1, 0) # lève une exception ZeroDivisionError
print ratio("1", "2") # lève une exception TypeError
print ratio(1., i) # lève une exception NameError 

La gestion d’une exception est découpée en plusieurs parties distinctes :

  • le déclenchement : raise (lance ou lève une exception)
  • le traitement (inspection et capture) : instructions inséparables try et except
  • la gestion peut être complétée par deux autres mots clés : finally (un bloc qui est exécuté après que tous les autres blocs aient été exécutés) et else (bloc exécuté si aucune exception n’est levée).

Forme minimaliste :

try:
    # instructions pouvant provoquer une exception
except: # attrape toutes les exceptions
    # instruction(s) exécutée(s) en cas d'exception

Forme complète :

try:
    # instructions pouvant provoquer une exception
except: # attrape toutes les exceptions
    # instruction(s) exécutée(s) en cas d'exception
else:
    # instruction(s) exécutée(s) si aucune exception n'est levée
finally:
    # instruction(s) toujours exécutée(s) à la fin

Il est possible et conseillé de préciser le type d’exception après le mot clé except :

ingredients = ['lait', 'farine', 'sucre', 'sel']

try:
    # si i est plus grand que la taille du tableau
    i = 4
    ingredient = ingredients[i]
except IndexError:
    ingredient = None

print ingredient # affiche None

Ou d’en indiquer plusieurs :

ingredients = ['lait', 'farine', 'sucre', 'sel']

try:
    # si i n'est pas un type entier
    i = "0"
    ingredient = ingredients[i]
except (IndexError,TypeError):
    ingredient = None

print ingredient # affiche None

Lever une exception :

def truc(a):
    if a == 0 :
        raise ValueError("a ne peut être égal à zéro")
    print a

try:
    truc(1)
    truc(-1)
    truc(0) # lève une exception ValueError
except ValueError as e:
    print e # a ne peut être égal à zéro

On peut aussi relancer une exception :

def ratio(num, den):
    try:
        if den == 0:
            raise ZeroDivisionError("Le dénominateur ne peut être égal à zéro")        
    except ZeroDivisionError:
            raise # on relance l'exception
    else:
        return num/den

try:
    print ratio(1., 2.) # affiche 0.5
    print ratio(1, 0) # lève l'exception ZeroDivisionError
except Exception as e:
    print(e) # affiche "Le dénominateur ne peut être égal à zéro"

Remarque : il est possible de créer ses propres (classes d’) exceptions en héritant de la classe Exception.

Les assertions

Les assertions sont un moyen simple de s’assurer, avant de continuer, qu’une condition est respectée. On utilise le mot clé assert : assert condition si condition est égale à True, l’exécution se poursuit normalement sinon, une exception AssertionError est levée :

#!/usr/bin/python
# coding: utf-8

def ratio(num, den):
    try:
        assert den != 0
    except AssertionError:
        return None
    else:
        return num/den

print ratio(1., 2.) # affiche 0.5
print ratio(1, 0) # affiche None

Classes

Définition de classe

Une classe se définit avec le mot-clé class.

Attention : Python oblige de déclarer l’instance de l’objet courant, conventionnellement nommée self, comme premier argument des méthodes, et à chaque fois que l’on souhaite accéder à une donnée de cette instance dans le corps de cette méthode.

La méthode __init__ permet d’initialiser une instance, elle est appelé automatiquement lorsqu’un objet a été créé. Il existe aussi __new__ qui est appelé avant la création de l’objet.

class Vehicule:
    """La classe Vehicule"""

    # un attribut
    couleur = ""

    # des méthodes
    def __init__(self, couleur="blanche"):
        self.couleur = couleur
    
    def avance(self): 
        """La méthode avance()"""
        print "j'avance"

    def tourne(self): 
        """La méthode tourne()"""
        print "je tourne"

    # une fonction spéciale
    def __repr__(self):
        """ L'affichage de l'objet dans l'interpréteur """
        return "Vehicule(couleur=\'" + self.couleur + "\')"

v1 = Vehicule()
v1.avance() # j'avance

# affiche le nom de la classe :
print v1.__class__.__name__ # Vehicule

# et son type :
print type(v1) # <type 'instance'>

print v1 # Vehicule(couleur='blanche')

Remarque : Le langage Python a un support très limité de l’encapsulation. Il n’y a pas, comme en Java ou C++ par exemple, de contrôle de l’accessibilité par des mots clefs comme protected ou private.

Les variables d’instance “privées” (private) auxquelles on ne peut accéder qu’à l’intérieur d’un objet n’existent donc pas en Python. Cependant, il existe une convention en Python :

  • un nom précédé d’un underscore (par exemple _b) doit être traité comme un membre non publique (qu’il s’agisse d’une fonction, d’une méthode ou d’un attribut).
  • un nom précédé de deux underscore (par exemple __c) sera textuellement remplacé par _classname__c, où classname est le nom de classe actuel (pour éviter les conflits de noms avec ceux définis par des sous-classes). Ceci fait que l’accès monObjet.__c provoquera une exception de type AttributeError et pourrait faire croire à une encapsulation privée ce qui n’est pas le cas).

Remarque : Le slogan des développeurs Python est « we’re all consenting adults here » (nous sommes entre adultes consentants) et qu’une simple convention suffira pour préciser les responsabilités (Source : fr.wikipedia.org).

Exemple :

class MaClasse:
    def __init__(self):
       # trois attributs :
       self.a = 1
       self._b = 2
       self.__c = 3

monObjet = MaClasse()

# affiche les attributs d'un instance :
print(vars(monObjet)) # {'a': 1, '_MaClasse__c': 3, '_b': 2}

# donc :
print(monObjet.a) # {'a': 1, '_MaClasse__c': 3, '_b': 2}
print(monObjet._b) # {'a': 1, '_MaClasse__c': 3, '_b': 2}
print(monObjet._MaClasse__c) # {'a': 1, '_MaClasse__c': 3, '_b': 2}

# mais :
print(monObjet.__c) # AttributeError: MaClasse instance has no attribute '__c'

Ensuite, le mécanisme des propriétés (property) permettra d’implémenter des accesseurs/mutateurs (getter/setter) : Manipuler les attributs.

Python reconnaît trois types de méthodes :

  • les méthodes d’instance, qui sont celles définies par défaut. Elles reçoivent comme premier argument une instance de la classe où elles ont été définies.
  • les méthodes de classe, qui reçoivent comme premier argument la classe où elles ont été définies. Elles peuvent être appelées depuis une instance ou directement depuis la classe. Elles sont déclarées avec le décorateur @classmethod.
  • les méthodes statiques, qui ne reçoivent pas de premier argument implicite. Elles sont similaires aux méthodes statiques que l’on trouve en Java ou C++. Elles sont déclarées avec le décorateur @staticmethod.

Remarque : Comme tout langage à typage dynamique, il n’est pas possible d’effectuer une surchage de fonctions ou de méthodes. Voir : Comment effectuer une surcharge ?

Fonctions spéciales et opérateurs

Python fournit un mécanisme pour définir un ensemble pré-défini d’opérateurs : tout objet Python peut se voir doté de méthodes dites spéciales.

Ces méthodes, commençant et finissant par deux tirets de soulignement (underscores), sont appelées lors de l’utilisation d’un opérateur sur l’objet : + (méthode __add__), += (méthode __iadd__), [] (méthode __getitem__), () (méthode __call__), etc. Des méthodes comme __repr__ et __str__ permettent de définir la représentation d’un objet dans l’interpréteur interactif et son rendu avec la fonction print. Il existe aussi : __new__ et __del__.

class Temps:
    def __init__(self, heure=0, minute=0, seconde=0):
        self.valeur = (heure*3600)+(minute*60)+seconde

    def __add__(self, a):
        temps = Temps()
        if type(a) == int:
            temps.valeur = self.valeur + a
        elif isinstance(a, Temps):
            temps.valeur = self.valeur + a.valeur                
        else: raise AttributeError, "aucun traitement pour le type " + str(type(a))
        return temps

    def __radd__(self, o):
        return self + o

    def __str__(self):
        return "{0:02}:{1:02}:{2:02}".format((self.valeur/3600), (self.valeur%3600)/60, (self.valeur%60))


t1 = Temps()
print("t1 = %s" % t1) # t1 = 00:00:00

t2 = Temps(1, 60, 30)
print("t2 = %s" % t2) # t2 = 02:00:30

# appel __add__ :
t1 = t2 + 10
print("t1 = %s" % t1) # t1 = 02:00:40

# appel _radd__ :
t1 = 20 + t2
print("t1 = %s" % t1) # t1 = 02:00:50

t1 = t1 + t2
print("t1 = %s" % t1) # t1 = 04:01:20

Héritage

Python supporte l’héritage (et l’héritage multiple).

class Voiture(Vehicule):
    """La classe Voiture"""

    def __init__(self, couleur="", model=""):
        Vehicule.__init__(self, couleur)
        # attributs
        self.model = model
        
    def afficherModel(self): 
        print self.model

    def tourne(self): 
        Vehicule.tourne(self)
        print "mais en douceur !"

v2 = Voiture("rouge", "306")
v2.afficherModel() # 306
v2.avance() # j'avance
v2.tourne() # je tourne mais en douceur !

# il faudrait redéfinir __repr__
print v2 # Vehicule(couleur='rouge')

setattr(v2, "model", "206")
print getattr(v2, "model")
v2.afficherModel() # 206

v3 = Voiture()
# il faudrait redéfinir __repr__
print v3 # Vehicule(couleur='')

Manipuler les attributs

Il existe plusieurs techniques pour manipuler les attibuts autrement que directement :

v2.model = "406"
print(v2.model) # 406
v2.afficherModel() # 406

Il est possible de lire ou de modifier un attribut dynamiquement avec les fonctions getattr() et setattr().

v2 = Voiture("rouge", "306")

setattr(v2, "model", "206")
print getattr(v2, "model") # 206
v2.afficherModel() # 206

Il est aussi possible de passer par des getter (accesseur) et des setter (mutateurs). Il faut que la classe hérite de object et que l’attribut soit préfixé par un underscore ou encore mieux deux underscore.

Il y a deux écriture possibles :

  • soit en passant par property
  • soit en utilisant @property

Exemple de getter (accesseur) et des setter (mutateur) :

# Version 1
class Personne(object):

    def __init__(self, nom=""):
        # un atribut pseudo privé :
        self.__nom = nom
   
    @property
    def nom(self):
        print "accesseur de nom"
        return self.__nom

    @nom.setter
    def nom(self, nom):
        print "mutateur de nom"
        self.__nom = nom
    
    @nom.deleter
    def nom(self):
        del self.__nom

p = Personne("Dupond")

# appel mutateur (setter) :
p.nom = "Durand"

# appel accesseur (getter) :
print(p.nom)

# ou :

# Version 2
class Personne(object):

    def __init__(self, nom=""):
        # un atribut pseudo privé :
        self.__nom = nom
   
    def getNom(self):
        print "accesseur de nom"
        return self.__nom

    def setNom(self, nom):
        print "mutateur de nom"
        self.__nom = nom
    
    def delNom(self):
        del self.__nom

    #nom = property(getNom, setNom)
    nom = property(getNom, setNom, delNom, "Je suis la propriété nom.")

p = Personne("Dupond")

# appel mutateur (setter) :
p.nom = "Durand"

# appel accesseur (getter) :
print(p.nom)

Les modules et les packages

Python permet la programmation modulaire.

Les modules sous Python sont des fichiers (.py) qui regroupent des ensembles de fonctions et/ou de classes.

Pour utiliser des modules dans un programme, il faut utiliser l’instructions import :

import math

print math.sqrt(4)

# ou en créant un alias :

import math as m

print m.sqrt(4) # 2.0

On peut aussi utiliser from mais cela est déconseillé pour des risques de conflits de noms :

from math import *

print sqrt(4) # 2.0

Il existe de nombreux modules standards dont : cgi, math, os, pickle, random, re, socket, sys, time, urllib, …

Il est évidemment possible de créer ses propres modules. Par exemple, un fichier fonctions.py qui contient les fonctions reponse et ratio vues précédemment.

Pour cela, il faut :

  • soit que le module existe sous la forme d’un fichier .py situé dans le même dossier que le fichier qui l’importe. Donc :
import fonctions

print fonctions.ratio(1.,2.)  # 0.5
  • soit qu’il soit dans un autre répertoire du programme et le module sera importé à partir du nom du répertoire.

Dans ce cas, Il faudra :

  • soit créer un fichier __init__.py qui assurera l’importation de tous les fichiers que le répertoire contient :
from fonctions import *

Puis, on importe le module lib (qui est le nom du répertoire qui contient le fichier fonctions.py) :

import lib

print lib.ratio(1.,2.)  # 0.5
  • soit modifier dynamiquement la liste des chemins en ajoutant les répertoires supplémentaires qui contiennent des modules à importer :
import sys 
sys.path.append("monchemin")

import fonctions

print fonctions.ratio(1.,2.)  # 0.5

Il est donc possible de regrouper des modules dans des packages. Comme pour d’autres langages, un package est tout simplement un répertoire. Ce répertoire pourra contenir d’autres répertoires (des packages) et des fichiers (des modules).

Remarque : Lors de l’importation d’un module, le programme va tout d’abord vérifier si le module à importer se trouve dans le dictionnaire sys.modules (module de base + les modules d’autres bibliothèques que vous avez installées). Si le module n’est pas trouvé, le programme le cherchera à partir de la liste définie par sys.path (qui contient le répertoire courant, la variable d’environnement PYTHONPATH entre autres).

Au moment d’importer le module, Python va lire (ou créer si il n’existe pas) un fichier .pyc (à partir de la version 3.2, ce fichier se trouve dans un dossier __pycache__). Ce fichier est généré par Python et contient du code compilé du module.

Si vous voulez ajouter du code au sein d’un module pour le tester par exemple, il sera utile de procéder ainsi :

#!/usr/bin/python
# coding: utf-8

def reponse(x): 
    """Affiche la réponse x"""
    print "La réponse est", x

def ratio(numerateur, denominateur=1): 
    """Retourne le ratio n/d"""
    return numerateur/denominateur

if __name__ == "__main__":
    print ratio(1., 2.)   # 0.5

Les modules sont des objets et tous les modules disposent de l’attribut prédéfini __name__. Le __name__ d’un module dépend de la façon dont vous l’utilisez. Si vous importez le module, son __name__ est le nom de fichier du module sans le chemin d’accès ni le suffixe. Mais vous pouvez aussi lancer le module directement en tant que programme, dans ce cas __name__ va prendre par défaut une valeur spéciale __main__.

Interfaces graphiques

Python possède plusieurs modules disponibles pour la création de logiciels avec une interface graphique. Le plus répandu est Tkinter. Ce module convient à beaucoup d’applications et peut être considéré comme suffisant dans la plupart des cas. Néanmoins, d’autres modules ont été créés pour pouvoir lier Python à d’autres bibliothèques logicielles (« toolkit ») : wxPython pour wxWidgets, PyQt pour Qt, …

Installer Tkinter :

sudo apt-get install python-tk python-imaging-tk

Exemple :

# -*- coding: utf-8 -*-

# on commence toujours par importer le module tkinter
# ici on lui donne le surnom (alias) de tk
# pour python3.x Tkinter devient tkinter

import Tkinter as tk

# il suffit alors de déclarer l'objet Tk()
# qui deviendra la fenêtre principale

fenetre = tk.Tk()

fenetre.title('Test Tkinter')

# on crée ensuite un objet Label()
# rattaché à fenetre
# pour afficher du texte non éditable
# on profite du constructeur de l'objet
# pour définir un texte "Hello World"
# dans la foulée (on peut faire autrement)

texte = tk.Label ( fenetre, text="Hello World" )

# l'objet Label() nommé texte est ensuite
# rendu visible dans fenetre grâce à pack()

texte.pack()

# un bouton

bouton = tk.Button(fenetre)
bouton.config(text='Quitter', command=fenetre.destroy)
bouton.pack()

# pour finir, on lance la boucle programme

fenetre.mainloop()

# that's all, folks!

Divers

Les fichiers

Pour lire le contenu d’un fichier, il faudra tout d’abord l’ouvrir (open()) puis le lire (read() qui retourne une chaîne de caractères ou readlines() qui retourne une liste de chaînes de caractères) et pour finir le fermer (close()) :

fichier = open('/etc/passwd','rb') # Ouverture du fichier en mode lecture 
lignes = fichier.readlines() # Récupération du contenu du fichier 
  
# Traitement ligne par ligne 
for ligne in lignes:  
    sp = ligne.split('#')[0] # Élimination des commentaires potentiels  
    sp = sp.split(':') # Séparation 
    #print sp
    print "Utilisateur : " +  sp[0] + " - UID : " + sp[2]

fichier.close() # Fermeture du fichier

Voir aussi la méthode seek() pour se déplacer dans un fichier.

Pour écrire dans un fichier, il suffit del’ouvrir (open()) puis d’écrire (write() pour une chaîne de caractères ou writelines() pour une liste de chaînes de caractères) et pour finir le fermer (close()) :

fichier = open('essai.txt','w') # Ouverture du fichier en mode écriture (avec écrasement)

fichier.write("hello world !") # Écriture d'un contenu dans le fichier 
  
fichier.close() # Fermeture du fichier

Voir aussi le module pickle pour enregistrer des objets dans des fichiers.

Remarque : les méthodes open() et close() ne sont pas obligatoires

fichier = file('essai.txt','r')

print fichier.read()

Pour récupérer la liste des fichiers d’un répertoire :

  • os.listdir(path) retourne une liste contenant les noms de tous les fichiers et répertoires de path
  • ou glob.glob(path) qui renvoie une liste contenant le chemin complet des fichiers ou répertoires contenus dans path
import glob  
import os.path

fichiers=[]  
repertoire = glob.glob('./*')  
for i in repertoire:  
    if os.path.isfile(i):
        fichiers.append(i)

print fichiers

Voir aussi : os.path.getsize() qui retourne la taille d’un fichier, os.remove() pour supprimer , os.rename() pour renommer , os.move() pour déplacer, os.chdir() pour changer de répertoire courant, … et le module ftplib pour dialoguer avec un serveur FTP.

Pour exécuter une commande, on fera :

import os

os.system("ls");

Voir aussi : les foncions du module os

Pour récupérer le résultat d’une commande, on fera :

import os

commande = os.popen('ls ./', 'r') # comme pour les fichiers, 'r' ou 'w'

print commande.read() # les mêmes méthodes qu'un fichier

Voir aussi : Popen de subprocess

Les expressions rationnelles

Python dispose d’un module re qui permet de manipuler des expressions rationnelles (regular expression).

Une expression rationnelle est une suite de caractères qu’on appelle plus simplement motif (pattern) pour trouver une correspondance (match). On les utilise dans le cadre d’une recherche ou d’un remplacement de texte. Les mécanismes de base pour former un motif sont basés sur des caractères spéciaux de substitution, de groupement et de quantification. Lire : fr.wikipedia.org

Exemple : un fichier texte contenant des numéros de téléphone

MARIE 04-91-85-96-34
ODILE 04-91-56-92-35
ALAIN 04-42-46-87-12

Utilisation de la fonction findall() :

#!/usr/bin/python
# coding: utf-8

import re

fichier = open('./numtel','rb') # Ouverture du fichier en mode lecture 
lignes = fichier.readlines() # Récupération du contenu du fichier 

regexp = r"((0[1-9])((-[0-9]{2}){4}))"

## Traitement ligne par ligne 
for ligne in lignes:  
    print ligne
    print re.findall(regexp, ligne)

Utilisation de la fonction match() :

#!/usr/bin/python
# coding: utf-8

import re

fichier = open('./numtel','rb') # Ouverture du fichier en mode lecture 
lignes = fichier.readlines() # Récupération du contenu du fichier 

regexp = r"((0[1-9])((-[0-9]{2}){4}))"

## Traitement ligne par ligne 
for ligne in lignes:  
    #print ligne
    sp = ligne.split(' ') # Séparation 
    
    if re.match(regexp, sp[1]) is not None:
        print "Nom : " +  sp[0] + " - Tél : " + sp[1]
    else:
        print "Numéro téléphone non trouvé !"

Utilisation de la fonction compile() :

#!/usr/bin/python
# coding: utf-8

import re

fichier = open('./numtel','rb') # Ouverture du fichier en mode lecture 
lignes = fichier.readlines() # Récupération du contenu du fichier 

regexp = r"((0[1-9])((-[0-9]{2}){4}))"
regex = re.compile(regexp)

## Traitement ligne par ligne 
for ligne in lignes:  
    #print ligne
    sp = ligne.split(' ') # Séparation 
    
    if regex.match(sp[1]) is not None:
        print "Nom : " +  sp[0] + " - Tél : " + sp[1]
    else:
        print "Numéro téléphone non trouvé !"

Utilisation de la fonction search() :

#!/usr/bin/python
# coding: utf-8

import re

fichier = open('./numtel','rb') # Ouverture du fichier en mode lecture 
lignes = fichier.readlines() # Récupération du contenu du fichier 

regexp = r"((0[1-9])((-[0-9]{2}){4}))"

## Traitement ligne par ligne 
for ligne in lignes:  
    #print ligne
    sp = ligne.split(' ') # Séparation 
    
    print re.search(regexp, sp[1]).groups()

Utilisation de la fonction search() en donnant des noms à des groupes :

#!/usr/bin/python
# coding: utf-8

import re

fichier = open('./numtel','rb') # Ouverture du fichier en mode lecture 
lignes = fichier.readlines() # Récupération du contenu du fichier 

regexp = r"((0[1-9])((-[0-9]{2}){4}))"

## Traitement ligne par ligne 
for ligne in lignes:  
    #print ligne
    sp = ligne.split(' ') # Séparation 
    
    m = re.search(r"(?P<numero>(?P<indicatif>0[1-9])((-[0-9]{2}){4}))", sp[1])
    print m.group('numero')
    print m.group('indicatif')

Utilisation de la fonction sub() pour remplacer le format 0X-XX-XX-XX-XX en 0X XX XX XX XX :

#!/usr/bin/python
# coding: utf-8

import re

fichier = open('./numtel','rb') # Ouverture du fichier en mode lecture 
lignes = fichier.readlines() # Récupération du contenu du fichier 

regexp = r"((0[1-9])((-[0-9]{2}){4}))"

## Traitement ligne par ligne 
for ligne in lignes:  
    #print ligne
    sp = ligne.split(' ') # Séparation 
    
    print re.sub(r"([0-9]{2})-([0-9]{2})-([0-9]{2})-([0-9]{2})-([0-9]{2})", r"\1 \2 \3 \4 \5", sp[1])

XML

Python propose le module lxml pour manipuler des fichiers XML.

XML signifie eXtensible Markup Language (Langage de balisage extensible). XML est un langage de description de documents standardisé par la spécification W3C XML 1.0 du 10/02/98.

Lien : cours XML

Un document XML bien formé signifie que le texte XML obéit aux règles syntaxiques de XML. Le document considéré comme valide (facultatif) signifie que le texte XML est bien formé et répond à une structure définie par une DTD (Definition Type Document).

Un document XML est structuré en trois parties :

  • un prologue : qui indique la version XML utilisée, le jeu de caractères (encoding) et la présence d’une DTD (standalone).
  • des instructions de traitement (IT) ou “processing instruction” (PI) : instructions relatives à des applications qui traiteront le document (feuille de style, transformation, …)
  • l’arbre des éléments : le contenu du document

Un document XML est composé d’éléments désignés par des balises et structuré sous la forme d’un arbre avec un et un seul élément racine (root). Les éléments sont aussi appelés noeuds ou nodes (par référence à la théorie des graphes).

Un prologue contient systématiquement une déclaration qui spécifie la version de XML utilisée : <?xml version="1.0" ?>. Il existe également deux autres attributs : encoding (pour définir le type de codage du jeu de caractères) et standalone (pour préciser si une DTD est utilisée avec no).

Le fichier interfaces.xml est le suivant :

<?xml version="1.0" encoding="UTF-8"?>
<interfaces>
  <interface id="0">
    <peripherique>/dev/ttyUSB0</peripherique>
  </interface>
  <interface id="1">
    <peripherique>/dev/ttyUSB1</peripherique>
  </interface>
</interfaces>

Lire le fichier interfaces.xml :

#!/usr/bin/python
# coding: utf-8

from lxml import etree

arbre = etree.parse("interfaces.xml")

# les éléments
for interface in arbre.xpath("/interfaces/interface/peripherique"):
    print(interface.text)

# les attributs
for interface in arbre.xpath("/interfaces/interface"):
    print(interface.get("id"))

Écrire un fichier XML :

interfaces = etree.Element("interfaces")

# les données à exporter en XML
donnees = [
("0", "/dev/ttyUSB0", "GPS"),
("1", "/dev/ttyUSB1", "Lecteur RFID"),
("2", "/dev/ttyUSB2", "DMX"),
("3", "/dev/ttyUSB3", "NMEA 0183"),
]

# création de l'arbre des éléments
for donnee in donnees:
    #print donnee
    interface = etree.SubElement(interfaces, "interface")
    interface.set("id", donnee[0])
    peripherique = etree.SubElement(interface, "peripherique")
    peripherique.text = donnee[1]
    description = etree.SubElement(interface, "description")
    description.text = donnee[2]

# affichage
print(etree.tostring(interfaces, pretty_print=True))
print(etree.tostring(interfaces, encoding='utf-8', method="xml", xml_declaration=True))

# écriture dans un fichier
arbre = etree.ElementTree(interfaces)
arbre.write("test.xml", encoding='utf-8', method="xml", xml_declaration=True)

Tutoriel : lxml.de

Transformer des scripts en exécutables

cx_Freeze transforme un script Pythoh en exécutable (Windows, Mac ou Linux). Pour Python 2.7, il faut faire :

$ sudo apt-get install python-dev python-pip
$ sudo pip install cx_Freeze
// ou :
$ sudo pip install cx_Freeze -i https://pypi.python.org/simple

Ensuite il faut créer un fichier setup.py :

from cx_Freeze import setup, Executable

# On appelle la fonction setup
setup(
    name = "votre_programme",
    version = "1",
    description = "Votre programme",
    executables = [Executable("votre_script.py")],
)

Puis il faut exécuter la commande suivante :

$ python setup.py build

Documentation : cx_freeze.readthedocs.org