Qu’est-ce que MVC et à quoi ça sert ? 23


MVC, pour “Modèle, Vue, Contrôleur”, est le nom donné à une manière d’organiser son code. C’est une façon d’appliquer le principe de séparation des responsabilités, en l’occurrence celles du traitement de l’information et de sa mise en forme.

Une fois n’est pas coutume je vais donner un exemple en Python et PHP, car c’est une question qui hante les codeurs de ce langage. En effet on leur rabâche qu’il faut utiliser MVC, que tel framework est MVC, que leur code à eux ne l’est pas, etc. Sans que nulle part, évidement, on ne donne une explication correcte de la notion.

Long article, petite musique.

(piqué à What the cut :-))

Principe de base

Il n’y a pas de Tables De La Loi qui disent ce qu’est le MVC, il y a donc autant de manières de le faire que de programmes. En fait, c’est un simple principe d’organisation de code, et il y en a d’autres. Mais généralement, c’est basé sur la répartition suivante :

  • Une part du code gère l’affichage. C’est la partie “Vue”.
  • Une part du code gère la manipulation des données. C’est la partie “Modèle”.
  • Tout le reste. L’espèce de merdier qu’on va mettre en place pour faire marcher le programme, c’est le contrôleur. Souvent, c’est le code qui réagit à l’action de l’utilisateur, mais pas seulement.

MVC est typiquement quelque chose d’abstrait qu’on ne peut pas comprendre avec une explication seule. Passons donc rapidement à un exemple.

Imaginons que l’on ait des tas de fichiers CSV ainsi faits :

"Jeu";"Nombre de joueurs Max";"Support"
"Secret of Mana";"3";"Super Nintendo"
"Bomberman";"8";"Super Nintendo"
"Mario Kart";"4";"Nintendo 64"
"Age of Empire 2";"8";"PC"

Et que nous voulions un programme qui fasse un rapport sur le CSV, affichant :

Nombre de jeux analysés : 10

Détails
--------

Support: Super Nintendo
Nombre de jeux : 2
Nombre de joueurs max : 8

Support: Nintendo 64
Nombre de jeux : 1
Nombre de joueurs max : 4

etc

Il y a de nombreuses manières de coder ce programme. Si on le fait en suivant le principe du modèle MVC, on va faire 3 fichiers : un pour le modèle, un pour la vue, et un pour le contrôleur. On peut avoir plus ou moins de 3 fichiers, j’ai choisi 3 fichiers pour bien illustrer le principe de séparation des responsabilités.

Le modèle

Le modèle manipule la donnée. Dans un site Web, le modèle est souvent le code qui permet de faire de requêtes à la base de données. Dans notre cas, c’est le code qui va manipuler le CSV. Encore une fois, il n’y a pas de définition divine de ce qu’est un modèle, ceci n’est qu’un exemple de ce que cela PEUT être. C’est le choix du dev.

modele.py

 
from __future__ import unicode_literals, absolute_import
 
from csv import DictReader
from collections import OrderedDict
 
class Modele(object):
 
    def __init__(self, csv):
        self.total_jeux = 0
        self.supports = OrderedDict()
        with open(csv) as f:
            # on parse le csv
            for data in DictReader(f, delimiter=b';', quotechar=b'"'):
                # on calcule les stats pour que ligne du csv
                support = self.supports.setdefault(data['Support'], {})
                support['nombre_de_jeux'] = support.get('nombre_de_jeux', 0) + 1
                self.total_jeux += 1
                if support.get('joueurs_max', 0) < data['Nombre de joueurs Max']:
                    support['joueurs_max'] = data['Nombre de joueurs Max']
 
    def __iter__(self):
        # goodies pour pouvoir itérer sur le modèle
        return self.supports.iteritems()

Ca s’utilise comme ça :

>>> modele = Modele("Bureau/jeux.csv")
>>> modele.total_jeux
4
>>> for support, data in modele:
    print support
    print data
...
Super Nintendo
{u'nombre_de_jeux': 2, u'joueurs_max': '8'}
Nintendo 64
{u'nombre_de_jeux': 1, u'joueurs_max': '4'}
PC
{u'nombre_de_jeux': 1, u'joueurs_max': '8'}

On voit ici le principe : le modèle ne fait que manipuler la donnée, et rien d’autre. Il extrait, regroupe, calcule, raffine, et donne une belle interface propre pour que le reste du programme puisse utiliser le résultat sans avoir à connaitre les détails du traitement.

La vue

La vue, c’est de la présentation. C’est comment on veut que la donnée soit présentée à l’utilisateur. Ça peut être le code qui pond du HTML ou produit un CSV, ou fait configurer de jolis boutons dans une UI.

Dans notre cas, c’est le code qui va formater le texte pour la console.

On veut un truc comme ça :

Nombre de jeux analysés : 10

Détails
--------

Support: Super Nintendo
Nombre de jeux : 2
Nombre de joueurs max : 8

Support: Nintendo 64
Nombre de jeux : 1
Nombre de joueurs max : 4

Normalement, on voudrait un template. Mais on a pas de langage de template qui accepte les boucles dans la lib standard, alors on va faire comme la norme WSGI et retourner un générateur de strings.

vue.py

from __future__ import unicode_literals, absolute_import
 
def rapport(modele):
    # affichage de l'en-tête
    yield ("Nombre de jeux analysés : {total_jeux}\n\n"
           "Détails\n--------\n").format(total_jeux=modele.total_jeux)
 
    # affichage des stats pour chaque console
    for support, data in modele:
        yield ("Support: {support}\n"
               "Nombre de jeux : {nombre_de_jeux}\n"
               "Nombre de joueurs max : {joueurs_max}\n").format(
               support=support, **data)

Et ça s’utilise comme ça :

>>> m = Modele("Bureau/jeux.csv")
>>> list(rapport(m))
[u'Nombre de jeux analys\xe9s : 4\n\nD\xe9tails\n--------\n', u'Support: Super Nintendo\nNombre de jeux : 2\nNombre de joueurs max : 8\n', u'Support: Nintendo 64\nNombre de jeux : 1\nNombre de joueurs max : 4\n', u'Support: PC\nNombre de jeux : 1\nNombre de joueurs max : 8\n']

Encore une fois, ceci n’est pas LA manière de faire une vue. Ceci est UNE manière de faire une vue. Le but de la vue est de contenir le code qui se charge de formater la donnée pour l’utilisateur.

Il est plus courant d’utiliser un template pour cela, c’est à dire une sorte lib de texte à trou à remplir plus tard avec le modèle. C’est plus facile et flexible qu’une fonction. Il y a des tas de libs de templates en Python. Je ferai sans doute un article dessus un jour. Si vous voulez un truc simple et rapide, utilisez templite : rien besoin d’installer, ça tient dans un fichier. Si vous voulez le truc le plus standard possible, utiliser jinja2, c’est plus ou moins la lib la plus connue actuellement.

Le contrôleur

Le contrôleur, c’est tout le reste. Essayer de définir le contrôleur est généralement voué à l’échec, tant sa nature change d’une application à l’autre. Certains disent que c’est le code glue qui permet de lier le modèle et la vue. D’autres qu’il contient la logique de flux du programme. Personnellement, je vous invite à vous fier à la définition “c’est tout le reste”. Avec l’expérience, vous en viendrez à faire des modèles et des vues de plus en plus adaptées, et la partie contrôleur découlera d’elle-même.

De toute façon, aucun MVC n’est parfait, et un peu de vue dégouline parfois sur le contrôleur, un peu de contrôleur coule dans le modèle, ou inversement. Il ne sert à rien d’être un nazi du MVC, c’est une bonne pratique, pas un dogme religieux.

Dans notre cas le programme a besoin d’un code qui :

  • Importe notre vue et notre modèle.
  • Prend en paramètre le fichier CSV via la ligne de commande.
  • Mélange tout ça pour afficher le résultat dans la console.

Le contrôleur est par ailleurs le point d’entrée d’un programme. Et ce sera essentiellement ça, le contrôleur de notre programme : un point d’entrée.

controlleur.py

from __future__ import unicode_literals, absolute_import
 
import os
import sys
 
from vue import rapport
from modele import Modele
 
# on prend le csv en paramètre du script
try:
    f = sys.argv[1]
except IndexError:
    sys.exit("Veuillez passer le chemin d'un fichier CSV en paramètre.")
 
# on vérifie que le csv existe
if not os.path.isfile(f):
    sys.exit("Le fichier '%s' n'existe pas" % f)
 
# on analyse le CSV et on affiche le rapport
for texte in rapport(Modele(f)):
    print texte

Résultat final

$ python controlleur.py jeux.csv
Nombre de jeux analysés : 4
 
Détails
--------
 
Support: Super Nintendo
Nombre de jeux : 2
Nombre de joueurs max : 8
 
Support: Nintendo 64
Nombre de jeux : 1
Nombre de joueurs max : 4
 
Support: PC
Nombre de jeux : 1
Nombre de joueurs max : 8

Vous pouvez télécharger le code Python de cet article.

Exemple en PHP

Le PHP a eu beaucoup de succès du fait de la facilité avec laquelle on pouvait coder un site Web, en mélangeant code et HTML. Malheureusement cela a donné lieu à des codes très sales, où on trouvait les requêtes SQL à côté de l’affichage d’un tableau, l’analyse des paramètres $_GET à deux pas de la vérification du mot de passe.

MVC a été une réponse à cela.

Un modèle MVC propre sera généralement très riche et complexe, mais il est possible de bricoler un site en MVC basique à la main sans trop de problème. Je ne vous recommande pas d’utiliser ce code en prod, mais c’est un bon début pour comprendre comment ça marche. Une fois que vous serez à l’aise avec l’idée, n’hésitez pas à coder le votre sur un petit projet, puis à tester un framework. Symfony, par exemple, est une valeur sûre en PHP.

Admettons que notre site ait deux pages : accueil et liste des utilisateurs.

L’accueil dit juste bonjour, la liste affiche tous les utilisateurs du site Web. Passionnant.

Le modèle

L’idée est de mettre toutes les requêtes SQL au même endroit.

Les vieux routards du PHP m’excuseront, mais je n’ai plus codé dans ce langage depuis des années, donc mon style va dater un peu :-) Et honnêtement tous ces points-virgules, ces dollars et ces brackets dans tous les sens, sans compter la flèche comme caractère de look up, ça me perturbe grandement.

modele.php

<?php
 
$con = mysqli_connect("127.0.0.1", 'root', 'admin123', 'ma_db');
 
class User {
 
    public $name;
    public $age;
 
    function __construct($name, $age) {
        $this->name = $name;
        $this->age = $age;
    }
 
    static function liste() {
 
        $users = array();
 
        $query =  mysqli_query($con, 'SELECT * FROM `user`');
 
        while ($row = mysql_fetch_assoc($query))
        {
            $users[] = User($row[0], $row[1]);
        }
 
        return users;
    }
 
}

Et ça s’utilise comme ça :

$users = User->liste();
foreach ($users as $user) {
    echo $user.name . '(' . $users.age. 'ans)';
}

Ce qui affiche tous les noms et les ages des utilisateurs.

Bien, on a isolé l’accès aux données, maintenant on va isoler la mise en forme.

La vue

Ou plutôt, les vues, puisqu’on a deux pages, et donc deux vues.

Vous ne le savez peut être pas, mais PHP vient avec une syntaxe alternative spécialement conçue pour être utilisée dans le HTML. Elle est similaire à la syntaxe originale, mais les blocs sont ouverts avec : au lieux de { et fermés par endinstruction. Les variables sont affichées avec <?=$nom_de_variable?>.

Par exemple:

<?php if $truc: ?>
    <p>
        <?=$machin?>
    </p>
<?php endif; ?>

Cette syntaxe permet de bien séparer le texte du code PHP, et donc sera utilisée pour la vue.

accueil.php

<html><body><h1>Bonjour</h1></body></html>

liste_utilisateurs.php

<html>
    <body>
        <h1>Utilisateurs</h1>
 
        <ul>http://www.php.net/manual/fr/control-structures.alternative-syntax.php
            <?php foreach $users as $user: ?>
                <li><?=$user->name?> (<?=$user->age?> ans)</li>
            <?php endforeach; ?>
        </ul>
 
    </body>
</html>

Et voilà, on a deux pages, et la deuxième affiche notre liste d’utilisateur. Vous remarquerez qu’il n’y a pas de requête ou de logique de choix de page, pas d’accès à mysql_* ou à $_GET dans ce code. Que de l’affichage.

Le contrôleur

Puisque le contrôleur, c’est le reste, ce sera à la fois notre point d’entrée, notre code glue et notre routing.

 
<?php
 
if (isset($_GET['page']) && $_GET['page'] == 'liste') {
    require 'modele.php'
    require 'liste_utilisateurs.php'
} else {
    require 'accueil.php'
}

Et c’est tout.

Si l’utilisateur va sur l’adresse monsite.com/, il va arriver sur l’accueil, si il va sur monsite.com/?page=liste, il va atterrir sur la liste des utilisateurs.

Si on veut changer le look de la page, on modifie la vue. Si on veut changer de base de source de données (et lire par exemple depuis un fichier), on change le modèle. Si on veut rajouter des pages, on change le contrôleur. L’avantage de la séparation des responsabilités, c’est la facilité de lecture et donc de maintenance et d’évolution.

J’insiste sur le fait que c’est un exemple pédagogique, et pas quelque chose à utiliser en prod (par exemple à cause des URLs très moches). Mais il va vous permettre de coder votre premier site en MVC, et plus tard, aller vers des versions plus sérieuses.

L’important, c’est la séparation donnée / formatage / reste du code.

MVC dans la vraie vie vivante

Créer un modèle MVC à la main propre et efficace, c’est énormément de taff. C’est pour cela qu’on utilise des outils tout fait comme des frameworks Web ou des libs graphiques (Qt, wxWidget et Gtk ont toutes des outils MVC, ex : Qt possède QML, un dialecte type CSS pour manipuler des vues).

Un modèle MVC simple, mais propre, est celui du micro-framework Web Python bottle, dont Max vous avait parlé ici. Lisez l’article, et revenez à ce paragraphe, et vous comprendrez que :

  • La vue, c’est le template.
  • Le modèle n’est inclus dans bottle, il faut le faire à la main ou utiliser quelque chose comme un ORM (peewee est très bien adapté).
  • Le contrôleur, ce sont les fonctions qu’on trouve sous les décorateurs @url.

Comme je l’ai dit précédemment, il y a de nombreuses manières de séparer le contenu de sa présentation.

Django par exemple n’utilise pas un modèle MVC au sens traditionnel, mais plutôt du MVT (Modèle – Vue – Template). Ce qu’il appelle les vues sont en fait ce qu’on appelle le contrôleur dans bottle : les fonctions qui mélangent les données avec le template. Django propose par contre un ORM, qui est bel est bien un système de modèle très élaboré.

C’est une question de sémantique, et au final, qu’importe le flacon, pourvu qu’on ait l’ivresse.

23 thoughts on “Qu’est-ce que MVC et à quoi ça sert ?

  • Lumin0u

    merci pour l’article et pour l’effort que ça doit être de faire du php :p
    d’ailleurs petite correction, si je ne m’abuse quand on accède à un membre d’objet en php, c’est $o->m plutôt que $o.m

  • Pierre

    Alors ça n’a absolument rien à voir avec l’article et ça ne mériterait certainement pas d’être le premier commentaire

    MAIS

    en lisant les jeux cités ‘Secret of Mana’, ‘Mario Kart’, ‘Age of Empire’, ‘Bomberman’ je me dis que l’âge de l’auteur de cet article, ou du moins de cette portion de code doit être entre 30 et 40 ans (plutôt moins de 33 d’ailleurs), qu’il aurait pu rajouter Dungeon Keeper, Counter Strike ou Fallout et, s’il a été l’heureux possesseur d’un Atari ST(E ou F) ou d’un Amiga au lieu d’un Nintendo ou d’une Megadrive, Populous ou encore Rick Dangerous. Nous pourrions également retracer son parcours informatique Console/Ordi puis Super Nintendo au pied du sapin avec Street Fighter puis Console/PC avec Windows 98 et premiers codes.

    (NB un gentil trombone me signale que je tiens des propos trollesques dès que je parle de Windows 98)

    Enfin c’est certainement le parcours de nombre d’entre-nous…

    Enfin mis à part ça, ‘Secret of Mana’ quand même, un sacré jeu…

    Voilà, maintenant, je retourne lire la fin de l’article.

  • dodoecchi

    Je plussoie Pierre : Secret of Mana super jeu. Si vous avez l’occasion de faire une partie entière en 2 players, foncez! On n’en fait plus des jeux comme ça… :(

  • Sam Post author

    @Lumin0u: merde, je croyais que j’avais fais un sans faute, mais j’ai trébuché sur la fin :-)

  • saajuck

    et var pour déclarer les variables en php entraînera une erreur.

    Par contre je souhaiterai rebondir sur :

    Exemple en PHP :
    [..]
    Un modèle MVC propre sera généralement très riche et complexe, mais il est possible de bricoler un site en MVC basique à la main sans trop de problème. Je ne vous recommande pas d’utiliser ce code en prod,

    Je me sens mal parce que c’est justement ce que je comptais faire pour un projet perso :/
    Je peux avoir quelques précisions ? ou des liens ?

    merci !

  • Syl

    @Pierre: Secret of Mana, Fallout, Dungeon Keeper…sniff, que de bons souvenirs!! (j’ai moins de 30 ans! ^^)

  • herison

    Salut à toi Sam

    Dans le MVC le modèle doit notifier la vue en cas de changement. Généralement on utilise le design pattern Observateur le modèle est un observable est les vues sont des observateurs attachés au modèle. Même chose sur la manière utilisée par la vue pour notifier le contrôleur lui même implémenté par le design pattern GRASP du même nom.

    Ca c’est la version d’origine du MVC celle utilisée dans les IHM des clients lourds comme swing, Après dans le web, le protocole HTTP ne permet pas la notification des vues par le modèle ou alors le serveur envoie des sms aux gens connectés en leurs demandant de faire F5. X)

    Bref tout ça pour dire que le MVC c’est bien adapté à son nouvel environnement qui est le web. Mais comment a t-il fait Oo ? A t-il encore des choses communes avec son ancêtre ? Si quelqu’un se sent de ns faire l’arbre évolutif du MVC ? :D

  • Recher

    Pour nos amis vieux et moins vieux, un article entier de ce blog est consacré aux jeux vidéos et aux souvenirs qui leur sont associés.

    http://sametmax.com/les-ordinnateurs-et-consoles-qui-ont-berces-notre-jeunesse-retour-sur-toute-une-epoque/

    D’ailleurs, dans les jeux vidéos, le MVC me semble assez galère à implémenter. Parce qu’il faut tout faire en même temps : afficher le jeu, prendre en compte les actions du joueur, prendre en compte les infos du réseau émanant d’autres joueurs, effectuer la logique du jeu, faire bouger les nichons de Lara Croft, …

  • Sam Post author

    Au contraire, ne pas implémenter du MVC dans un jeu vidéo, c’est impossible. Simplement le modèle est un modèle live, et la vu est le moteur 3D.

  • agateau

    Juste pour faire un peu le relou: QML n’est pas un dialecte XML. Ça ressemble plutôt à du CSS imbriqué, ou du JSON sans les guillemets inutiles (et avec “:” à la place de “=”)

  • raphi

    Chouette cette intro \o/

    Y’a quelques mois j’ai torché un petit exo bidon qu’une boite m’avait filé et j’ai essayé d’implementer un modele MVC en m’inspirant pas mal de l’orga de django. Ca a donné un truc assez crado (manque de temps, envie d’experimenter avec l’objet en PHP que j’connais très mal…) mais si ca en interesse certains, le code est la
    (Je suis preneur si y’a des avis aussi, autant j’ai pas des masses envie d’me remettre a PHP de sitot autant ca servira toujours de progresser un peu la dedans).

    Sinon j’avoue que j’ai jamais trop pigé le coup du MTV de django. J’ai lu leur explication, mais j’avoue qu’elle m’a pas des masses convaincue. Dans l’absolu on s’en fout, c’est juste assez confusant quand on en discute avec quelqu’un qui ne connait pas le framework.

  • MrAaaah

    Bon petit article, je me souviens avoir dû m’y prendre à plusieurs reprises avant de bien comprendre comment bien organiser mon code en mvc (avec CakePHP), un article d’introduction clair et concis comme ça c’est top.

    Sinon petite coquille au niveau de la partie du contrôleur :

    Dans notre cas le programme a besoin d’un code qui :

    Importe notre contrôleur vue et notre modèle. :

    @Recher & @Sam : Le modèle MVC me semble pas très répandu dans les JV, après je sais pas comment ça ce passe pour les gros projets.
    On a plutôt tendance à tout garder bien indépendant et updater chaque composant séparément (moteur physique, affichage, logique du jeu, réseau, inputs, etc.) quand il est temps. Par exemple on ne rafraichit pas une IA aussi souvent que l’affichage. Après t’a es cas (notamment UDK il me semble) où tout les objets du jeu sont dans des threads indépendants.

    Niveau organisation du code, pour avoir fait 4-5 projets avec UDK et Unity3d (projet de 48h à 15 semaines) j’ai jamais croisé de modèle MVC (du moins jamais sous la forme que je connais). Par contre dans les deux y’a une utilisation massive des composants.
    (j’ai pas de grosses connaissances des designs pattern ni dans la conception d’un moteur de jeu, donc je dit p-e des conneries sur certains points ^^)

    (bon petit choix de musique au passage, mais le top c’est ces démos avec Razor1911)

  • Marien

    Juste deux trucs rapides :
    – mysql_connect est obsolète (http://www.php.net/manual/fr/function.mysql-connect.php)
    – J’évite généralement la partie “syntaxe alternative”. Je croyais qu’elle était dépréciée, à priori non. En tout cas je me suis déjà retrouvé avec des serveurs configurés pour ne pas la proposer, gaffe donc

    Sinon c’est un peu dommage de se concentrer sur l’aspect web même si je comprends bien l’intérêt. Comme dit herison plus haut, le MVC pour les clients lourds est différent du MVC web en ce qui concerne la gestion de la vue. Je n’ai jamais trouvé de bon article expliquant bien tout ça (jamais vraiment cherché peut-être aussi…)

    Et pour reprendre la discussion à propos des jeux vidéo, vu sur LinuxFR une alternative qui est à priori plus adaptée : les systèmes à entités (http://linuxfr.org/news/je-cree-mon-jeu-video-e01-les-systemes-a-entites)

  • Sam Post author

    @Pierre: l’analyse est très bonne, tu peu rajouter chaoes engine, dune, monkey island, prince of persia, etc.

    @herison : restons dans un truc qui est utilisable au quotidien. L’arbre, à pars le bruler pour se tenir chaud…

    @agateau : ok, je corrige ça.

    @raphi: voit le de cette magnière : si tu dois générer un PDF, tu vas pas avoir de template. Du coup ta vue elle est où ?

    @MrAaaah: merci pour la correction. Et effectivement, j’ai dis une connerie. C’est une forme de séparation des responsabilités et du rendu / vs modèle, mais c’est pas du MVC. Y a de l’observer, de l’entity, etc.

    @Marien : ok je vais mettre ça à jour. Par contre la syntaxe alternative, si on a pas de moteur de template bien entendu, c’est vachement plus facile à lire. Si un hébergeur le desactive, faut quitter l’hébergeur, c’est un con.

    Quand à l’aspect Web, l’exemple en Python n’est pas Web…

  • Marien

    Si un hébergeur le desactive, faut quitter l’hébergeur, c’est un con.

    Je suis d’accord que l’hébergeur est un con mais si t’as payé ton hébergement et que tu dois le quitter le lendemain juste pour ça, ça vaut pas le coup. Du coup si on veut rendre notre code le plus portable possible il vaut mieux éviter. À noter que les short tags ne sont plus désactivables depuis PHP 5.4, mais évidemment ça rajoute une dépendance sur une techno assez peu déployée (http://www.php.net/manual/en/ini.core.php#ini.short-open-tag) Aucune idée pour la syntaxe alternative.
    Le mieux reste encore de ne pas faire de PHP et se mettre à Python :)

    Au temps pour moi sur l’aspect non web de l’exemple Python, j’ai écrit trop vite sans faire attention. Ce que je voulais dire c’est que l’exemple c’est du “one shot” : tu l’exécutes et la vue n’a plus à se mettre à jour ensuite contrairement généralement à une interface graphique. Mais je comprends parfaitement que ça sorte de l’objectif de présenter simplement MVC, je voulais juste apporter une précision dans les commentaires :)

  • Symen

    Article intéressant et rassurant.
    J’avais l’impression de me mélanger les pinceaux entre les vues, modèles et contrôleurs mais apparemment je m’en sors pas si mal. :)

  • L'autre Pierre

    Merci pour cet excellent article !

    J’ai enfin compris pourquoi je ne comprenais pas. En l’occurrence, j’avais du mal à capter la définition du contrôleur.

    Merci de nous avoir fourni une définition claire, efficace et sans ambiguïté : « Le contrôleur, c’est tout le reste. »

Leave a comment

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <pre> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Des questions Python sans rapport avec l'article ? Posez-les sur IndexError.