Skip to article frontmatterSkip to article content

types prédéfinis et monde réel

les types prédéfinis que l’on a vus jusqu’ici
i.e. nombres, chaines, containers
(pour rappel: bool, int, float, str, list, set, dict)
sont pratiques et puissants

MAIS souvent cela n’est pas suffisant pour traiter des problèmes réels

typiquement

on a très souvent besoin de manipuler des données ‘composites’
e.g. un ensemble de personnes, de villes, de compagnies aériennes
en termes de modèle de données, ce sont des ‘enregistrements’
c’est-à-dire en fait des données composites

# une façon naive d'implémenter une donnée composite
# est d'utiliser un dictionnaire

personne = {'name': 'Dupont', 'age': 32}

utiliser un dictionnaire - ou un tuple - peut faire l’affaire
pour des applications simples
(c’est par exemple ce qu’on récupère d’un fichier JSON)
mais c’est vite un peu lourd et très limité

class

dans ces cas-là (et dans bien d’autres)
il est plus flexible de se définir un nouveau type
qui permette de créer des objets qui ont les propriétés en question
que dans ce contexte on appelle des attributs

# voici comment définir
# une classe `User`

class User:

    def __init__(self, name, age):
        self.name = name
        self.age = age
# une fois qu'on a défini une classe,
# on peut s'en servir pour créer
# des objets - on dit des instances
# de la classe

user1 = User("Lambert", 25)

instances

remarquez qu’ici

def __init__(self, name, age)
User("Lambert", 25)`

affichage

# si on ne fait rien de spécial,
# l'affichage est vraiment vilain
user1
# qui vous dit que user1 est un objet dans le module __main__
# qu'il est de de type User
# et où il est dans la mémoire de votre ordi
# mais ce n'est sûrement pas ce que vous voulez voir !
<__main__.User at 0x7fd84c16dd60>
# pour améliorer cela:
# vous définissez la représentation de vos objets de type User
class User:

    def __init__(self, name, age):
        self.name = name
        self.age = age

    # définit comment afficher
    # les objets de cette classe
    def __repr__(self):
        return f"{self.name}, {self.age} ans"
user2 = User("Martin", 22)
user2
Martin, 22 ans

méthodes

comme on l’a évoqué dans la section “objets, types et méthodes”
dans une classe on peut définir des méthodes
qui sont des fonctions qui s’appliquent sur un objet (de cette classe)

# une implémentation très simple
# d'une file FILO
# premier entré dernier sorti

class Stack:

    def __init__(self):
        self.frames = []

    def push(self, item):
        self.frames.append(item)

    def pop(self):
        return self.frames.pop()

    def __repr__(self):
        return " > ".join(self.frames)
# instance
stack = Stack()

stack.push('fact(3)')
stack.push('fact(2)')
stack.push('fact(1)')

stack
fact(3) > fact(2) > fact(1)
stack.pop()
'fact(1)'
stack
fact(3) > fact(2)

méthodes - suite

à nouveau, remarquez que la définition d’une méthode
prévoit un paramètre de plus que l’appel de la méthode
car obj.methode(...)
est équivalent à methode(obj, ...)

intérêts de cette approche

objets et langage

le langage “connaît” bien sûr les types prédéfinis
c’-à-d qu’il sait “faire la bonne chose”
selon le type des objets qu’il manipule

exemples, selon le type de obj :

exemple - if obj:

remarquez qu’on peut toujours écrire un test if (ou while) même si le sujet du test n’est pas un booléen

if 0:
    print('bingo')
if 2:
    print('bingo')
bingo
if []:
    print('bingo')
if [1]:
    print('bingo')
bingo

la règle pour les types prédéfinis est que dans un test
0, 0.0, "", [], set(), {} sont considérés comme False
les autres valeurs sont considérées comme True

méthodes spéciales

les méthodes spéciales sont toutes de la forme __bidule__
on en a déjà vu 2 : __init__ et __repr__
le langage en prévoit plusieurs dizaines (optionnelles)

exemple de méthode spéciale

exemple
lorsqu’un objet est utilisé dans un if obj:
il est d’abord converti en booléen par bool(obj)

pour redéfinir ce que cela veut dire sur une classe
on peut définir la méthode spéciale __bool__

dans l’exemple qui suit, je définis une classe Person
et je décide (uniquement à but pédagogique)
que l’instruction if person:
va s’exécuter avec les gens dont le nom n’est pas nobody

# une classe jouet
# uniquement à but pédagogique ...

class Person:

    def __init__(self, name, age):
        self.name = name
        self.age = age

    # cette méthode est appelée lorsqu'on
    # a besoin de convertir un objet en booléen
    # e.g. quand on fera
    # if person:
    #    blabla
    def __bool__(self):
        return self.name != 'nobody'
real_person = Person('Dupont', 32)
fake_person = Person('nobody', 0)
if real_person:
    print('YES !')
YES !
# dans ce cas il ne passe rien
# car notre instance est convertie en
# booléen et çca donne 'False'
if fake_person:
    print('NOPE')
# c'est-à-dire que
bool(real_person)
True
bool(fake_person)
False

résumé

la classe Quaternion - avancés

En option

on peut aussi redéfinir les opérateurs arithmétiques
comme + et * avec les
méthodes spéciales __add__ et __mul__

Application: une micro-classe qui implémente les quaternions

class Quaternion:

    def __init__(self, a, b, c, d):
        self.implem = (a, b, c, d)  
    # c'est la partie intéressante
    def __add__(self, other):
        """defines q1 + q2)"""
        return Quaternion(*(x+y for x, y in zip(self.implem, other.implem)))  
    def __mul__(self, other):
        """defines q1 * q2"""
        a1, b1, c1, d1 = self.implem
        a2, b2, c2, d2 = other.implem
        a = a1 * a2 - b1 * b2 - c1 * c2 - d1 * d2
        b = a1 * b2 + b1 * a2 + c1 * d2 - d1 * c2
        c = a1 * c2 + c1 * a2 + d1 * b2 - b1 * d2
        d = a1 * d2 + d1 * a2 + b1 * c2 - c1 * b2
        return Quaternion(a, b, c, d)  
    def __eq__(self, other):
        """implements q1 == q2"""
        return self.implem == other.implem  
    def __repr__(self):
        a, b, c, d = self.implem
        return f"({a}, {b}, {c}, {d})"
q0 = Quaternion(0, 0, 0, 0)
q1 = Quaternion(1, 0, 0, 0)
q_1 = Quaternion(-1, 0, 0, 0); q_1
(-1, 0, 0, 0)
i = Quaternion(0, 1, 0, 0)
j = Quaternion(0, 0, 1, 0)
k = Quaternion(0, 0, 0, 1)
k
(0, 0, 0, 1)
i * i
(-1, 0, 0, 0)
i*i == j*j == k*k == i*j*k == q_1
True
q = Quaternion(1, 2, 3, 4)
q * q
(-28, 4, 6, 8)
q * i
(-2, 1, 4, -3)
i * q
(-2, 1, -4, 3)

limitations pour cette version rustique :

    # pour affichage
    labels = ['', 'i', 'j', 'k']  
    # un code possible pour un affichage plus élégant

    # affichage
    def __repr__(self):
        # on prépare des morceaux comme '3', '2i', '4j', '5k'
        # mais seulement si la dimension en question n'est pas nulle
        parts = (f"{x:.1f}{label}" for x, label in zip(self.implem, labels) if x)

        # on les assemble avec un + au milieu
        full = " + ".join(parts)

        # si c'est vide c'est que self est nul
        return full if full != "" else "0"

        # ce qui donnerait aussi en version un peu plus condensée
        # mais beaucoup moins lisible
        # return (" + ".join(f"{x:.2f}{label}" for x, label in zip(self.implem, labels) if x) or "0")