Python ou rien

Un article de Haypo.

Retour à la page précédente Retour aux langages de programmation

Ce document se veut être un document concis expliquant pourquoi Python est un langage surpuissant :-) (rien que ça !)

Sommaire

[modifier] Structures de données

Les tableaux (également appelés vecteurs ou listes), les dictionnaires (tableaux associatifs) et chaînes de caractères existent de base. Ce n'est pas une bibliothèque externe comme en C++ par exemple. De plus, ce sont des objets, on peut donc leur appliquer des méthodes :

# Chaîne de caractère : Affiche "Bonjour Victor"
print " Hello Victor ".replace("Hello", "Bonjour").strip()

# Tableau : Affiche [1, 3]
nombres = [1]
nombres.extend([2, 3])
nombres.remove(2)
print nombres

# Dictionnaire : Affiche "one"
anglais = {1: "one", 2: "two"}
print anglais[1]

D'ailleurs, on peut utiliser l'unicode sans difficulté : voir la notation « u"accentué" », la méthode « "accentué".decode("utf-8") » et la fonction équivalente « unicode("accentué", "utf-8") ». Il existe également les tuples qui sont des tableaux de taille fixe (exemple : (1, 2, 3)) qui utilisent moins d'espace mémoire.

Les tableaux et tuples sont très bien intégrés dans le langage. On peut assigner plusieurs valeurs à la fois et une fonction peu renvoyer plusieurs valeurs très naturellement.

>>> x, y, z = 10, 5, 2
>>> def div_mod(x, y):
...   return x/y, x%y
...
>>> div_mod(14, 5)
(2, 4)
>>> texte = "Tout est bon dans le Python"
>>> tout_est_bon, python = texte[:12], texte[21:]

[modifier] Générateur et boucle « pour chaque »

« Générateur » avec la méthode yield et boucle « pour chaque » (« for each » en anglais) avec for x in … :

def fibonacci():
    a = 0
    b = 1
    while True:
        yield a
        a, b = b, a+b

n = 0
for x in fibonacci():
    print "u(%s)=%s" % (n, x)
    n = n + 1
    if n == 10:
        break

La fonction fibonacci renvoie un « générateur » qui peut être lu comme on lirait un fichier et est infini. Nul besoin de stocker les (deux) termes de la suite de l'italien surnommé Fibonacci, Python le fait pour nous.

Autre exemple avec la boucle « pour chaque », numérotation d'un fichier texte :

n = 1
for ligne in open("fichier.txt", "r").readlines():
    print "%u: %s" % (n, ligne.rstrip("\r\n"))
    n += 1

[modifier] Langage dynamique

[modifier] Introspection

Exemple d'introspection :

class Personne:
    def __init__(self, prenom, nom):
        self.prenom = prenom
        self.nom = nom

    def __str__(self):
        return "%s %s" % (self.prenom, self.nom)

# Ajoute un attribut non prévu dans la classe
victor = Personne("Victor", "Stinner")
victor.age = 22

# Modification et lecture d'un attribut en donnant son nom
# sous forme d'une chaîne de caractère
setattr(victor, "age", victor.age+1)
print "%s a %s ans" % (victor, getattr(victor, "age"))

# On peut avoir la liste des attributs d'une classe avec :
print victor.__dict__

Résultat :

Victor Stinner a 23 ans
{'nom': 'Stinner', 'age': 23, 'prenom': 'Victor'}

Voir aussi le module inspect.

[modifier] Typage dynamique

Changement de type automatique :

>>> n=10
>>> type(n)
<type 'int'>
>>> n = n*100000000000000000000
>>> type(n)
<type 'long'>

Méthode qui s'en fiche du type, appellée « template » en C++ ou encore méthode générique :

>>> liste=[]
>>> def ajoute(x):
...     liste.append(x)
... 
>>> ajoute(10)                  # Ajoute un nombre
>>> ajoute("abc")               # Ajoute une chaîne de caractères
>>> ajoute({"blanc": "noir"})   # Ajoute un dictionnaire
>>> print liste
[10, 'abc', {'blanc': 'noir'}

[modifier] Surchage d'opérateur

Python donne la possibilité de faire de la programmation orientée objet. Beaucoup d'opérateurs et fonctions sont surchargeables :

class ObjetEnPython:
    # Constructeur
    def __init__(self):
        self.items = [1, 2, 3]

    # Destructeur
    def __del__(self):
        print "BOUM"

    # Utilisé pour : "cmp(p1, p2)", exemple : tri d'une liste de p avec sort()
    def __cmp__(a, b):
        return cmp(a.items, b.items)

    # Utilisé pour : "str(p)", exemple : "print p"
    def __str__(self):
        return "liste = %s" % self.items

    # Utilisé pour : "len(p)"
    def __len__(self):
        return len(self.items)

    # Utilisé pour : "p[x]"
    def __getitem__(self, key):
        return self.items[key]

    # Utilisé pour : "x in p"
    def __contains__(self, value):
        return (value in self.items)

    # Utilisé pour : "for x in p"
    def __iter__(self):
        for x in self.items:
            yield x

On peut également surcharger les opérateurs arithmétiques (a+b, a*b, ...). Quelques jolis exemples (dans l'interpréteur Python) :

>>> print "zZ" * 3          # Surchage de l'opérateur a*b
zZzZzZ
>>> from path import path   # Bibliothèque path d'Orendorf
>>> usr = path("/usr")
>>> print usr/"bin"         # Surcharge de l'opérateur a/b
/usr/bin

[modifier] Comparatif avec d'autres langages

[modifier] Comparatif avec le C++

  • Le C++ possède également les vecteurs, listes, tables de hachage, itérateurs, l'opérateur « pour chaque » (bien que lourd à utiliser), etc.
  • La gestion des exceptions existe, mais il manque finally (que Borland a ajouté sans son compilateur) et on ne peut pas récupérer la pile d'appel.
  • Il manque les properties (Borland les a ajouté à son compilateur)
  • La gestion des paquetages est très lourde : il faut dupliquer les commentaires et prototypes dans les fichiers d'entêtes, extension ".h". Par contre, il existe les espaces de nom (namespace).
C++ Python
#include <string>
#include <vector>
#include <iostream>

class Personne {
    std::string prenom, nom;
public:
    Personne(const std::string &prenom, const std::string &nom) {
        this->prenom = prenom;
        this->nom = nom;
    }
    friend std::ostream& operator<< (std::ostream&, const Personne&);
}

std::ostream& operator<< (std::ostream &os, const Personne &p) {
    os << p.prenom << ' ' << p.nom;
    return os;
}

void test() {
    std::vector<Personne> liste;
    liste.push_back(Personne("Victor", "Stinner"));
    liste.push_back(Personne("Damien", "Boucard"));
    for (std::vector<Personne>::iterator it=liste.begin(); it != liste.end(); ++it) {
        std::cout << *it << std::endl;
    }
}
class Personne:
    def __init__(self, prenom, nom):
       self.prenom = prenom
       self.nom = nom

    def __str__(self):
       return "%s %s" % (self.prenom, sel.nom)

def test():
    liste = [Personne("Victor", "Stinner"), Personne("Damien", "Boucard")]
    for personne in liste:
       print personne

[modifier] Comparatif avec le Java

  • Java possède également les vecteurs, listes, tables de hachage et itérateurs. Par contre avant Java 1.5, l'opérateur « pour chaque » manquait et les cast alourdissaient le code.
  • La gestion des exceptions est très bonne, finally existe également et on peut récupérer la pile d'appel. Par contre, il faut explicitement attraper les exceptions pouvant être levées et les renvoyer si on ne peut pas les traiter tout de suite. C'est un choix de Sun.
  • La gestion des paquetages est très propre en Java, mais par contre une seule classe par fichier est autorisée (correctif: une seule classe publique par fichier)
  • Java supporte également les decorators (Python 2.4 s'en est largement inspiré)
  • Il manque les properties, mais ceci est un choix de Sun
Java Python
import java.util.Vector;
import java.util.Iterator;

class Personne {
    String prenom, nom;

    public Personne(String nom, String prenom) {
        this.nom = nom;
        this.prenom = prenom;
    }

    public String toString() {
        return this.prenom+" "+this.nom;
    }
}    

void test() {
    Vector liste = new Vector();
    liste.addElement( new Personne("Victor", "Stinner") ); 
    liste.addElement( new Personne("Damien", "Boucard") ); 
    for (Iterator it = liste.iterator(); it.hasNext(); ) {
        System.out.println( it.next() );
    }
}
class Personne:
    def __init__(self, prenom, nom):
        self.prenom = prenom
        self.nom = nom

    def __str__(self):
        return "%s %s" % (self.prenom, sel.nom)

def test():
    liste = [Personne("Victor", "Stinner"), Personne("Damien", "Boucard")]
    for personne in liste:
        print personne

[modifier] Programmation orientée objet

Python supporte l'héritage multiple, la visibilité (public, privé, protégé) et les propriétés (property). Ces dernières sont vraiment très pratiques, et je ne comprend pas pourquoi cela n'a pas été ajouté au C++. Pour Java, Sun a choisi l'explicite en interdisant par exemple la surcharge des opérateurs. C'est un choix, mais je trouve ça bien dommage.

Les propriétés sont une invention géniale permettant d'ajouter des traitements lors de l'accès en lecture sur un « attribut » d'un objet et sur la modification d'un « attribut » d'un objet. Je met attribut entre guillemets car on peut avoir des attributs fictifs ou calculés. De plus, on peut interdire la modification ou la lecture.

Exemple sans propriété avec attributs publics :

class Animal:
    def __init__(self, nom, age):
        self.nom = nom
        self.age = age

    def __str__(self):
        return "%s (%s ans)" % (self.nom, self.age)

maurice = Animal("Poisson", 2)
print maurice
maurice.age = "ooops"
print maurice

Résultat :

Poisson (2 ans)
Poisson (ooops ans)

Le problème est qu'on peut entrer n'importe quelle valeur pour l'âge, pas forcément un nombre entier positif. On va donc ajouter des tests :

class Animal(object):
    def __init__(self, nom, age):
        self.nom = nom
        self._age = age

    def _getAge(self):
        return self._age
    def _setAge(self, age):
        assert isinstance(age, int)
        assert 1 <= age <= 200
        self._age = age
    age = property(_getAge, _setAge)

    def __str__(self):
        return "%s (%s ans)" % (self.nom, self.age)

maurice = Animal("Poisson", 1)
maurice.age = "ooops"  ~~~> BOUM, une exception est levée
maurice.age = -7       ~~~> BOUM, une exception est levée

L'attribut age est passé en protégé (accessible aux fils mais pas en dehors de l'objet) simplement en ajoutant "_". Si l'on avait voulu le passer en privé, il aurait fallu ajouter utiliser le nom "__age".

Le gros avantage de propriétés et qu'on garde la même API publique, par contre le désavantage est qu'on est obligé d'écrire un getter et un setter.

[modifier] À compléter

  • Gestion des erreurs
  • Documentation
  • Paquetage
  • Héritage d'une ou plusieurs classes
  • Property, private & protected

[modifier] Voir aussi