Fichier de rejeu Close

Indication Close

A propos de... Close

Commentaire Close

Téléchargements

Aide

Programmation procédurale

Objectif :

L’objectif de ce cours est d’acquérir un certain nombre de bonnes pratiques concernant la programmation procédurale. Il s’agit principalement de comprendre le principe de la barrière d’abstraction. Pour valider cette objectif il faudra être capable d’écrire correctement le module correspondant à un type abstrait de donnée.

Programmation descendante

Nous nous appuyons sur le paradigme de programmation procédurale. Il est impensable de réaliser le projet avec un code purement séquentiel sans le structurer en de nombreuses fonctions. Comme prérequis à ce cours, vous devez donc maîtriser parfaitement ce qu’est une fonction, comment on la définit, comment on lui passe des paramètres, comment elle renvoie une valeur de retour, quelle est la portée de ses variables locales.

rappel sur appel d'une fonction
python : code_run356.py
Sorties

            

Découper un problème en sous problèmes de plus en plus simples jusqu’à ce que tout soit facilement manipulable, correspond à la méthode réductionniste très largement utilisée en sciences. Vous savez décomposer un programme en fonctions/procédures. Il s’agit alors de découper le programme jusqu’à ce qu’il ne soit plus composé que de fonctions simples à comprendre, écrire, modifier ou corriger. Dites vous que si le code d’une fonction est trop long pour être entièrement affiché à l’écran, il y a de forte chance pour que cela vaille la peine de la décomposer en sous fonctions. Enfin, le fait qu’une fonction puisse être appelée plusieurs fois dans différents contextes permet la factorisation du code. On évite ainsi la redondance de code.

Lorsqu’on écrit une fonction on procède par étapes. Cela permet d’avancer de manière cohérente dans le codage et de pouvoir s’interrompre pour reprendre plus tard.

  1. Prototype:

D’abord on écrit le prototype de la fonction. C’est à dire : son nom, ce qu’elle prend en paramètre et ce qu’elle renvoie (rien dans cet exemple ci-après). Le nom d’une fonction doit être un verbe (do()) ou un groupe verbale (doIt()) cohérent avec l’action réalisée par la fonction.

def show(grid):
    return
  1. Commentaires:

Ensuite à l’aide de commentaires on peut indiquer le but de la fonction puis la trame de l’algorithme à réaliser. Cela permet de réfléchir à ce qu’on va faire avant de se lancer bille en tête dans le code.

def show(grid):
    #affiche une image contenue dans grid

    #effacer la console

    #pour chaque case de la grille

    #si il y a un caractere a afficher

    #deplacer le curseur au bon endroit

    #ecrire caractere

    #ecrire un saut de ligne

    return
  1. Structures de contrôle (for, if...) et appels des sous fonctions

Puis, on commence à écrire les structures de contrôle, les conditions si elle sont simples puis on commence à indenter le code.

def show(grid):

    #effacer la console
    clearTerm()

    #pour chaque case de la grille
    for i in range(len(grid)):
        for j in range(len(grid[i])) :

            #si il y a un caractere a afficher
            if():

                #deplacer le curseur au bon endroit
                moveCursor()

                #ecrire caractere
                write(c)

    #ecrire un saut de ligne
    write('\n')
    return
  1. Codage des détails:

On finalise le code.

def show(grid):

    #effacer la console
    clearTerminal()

    #pour chaque case de la grille
    for i in range(len(grid)):
        for j in range(len(grid[i])) :

            c=grid[i][j]
            #si il y a un caractere a afficher
            if(not (c==None or c=='')):

                #deplacer le curseur au bon endroit
                moveCursor(i,j)

                #ecrire caractere
                stdout.write(c)

    #ecrire un saut de ligne
    stdout.write('\n')
    return
  1. Tests

    Voir la section sur les tests.

Arbre d’appels

Afin de bien comprendre le fonctionnement d’un programme il est possible de représenter les appels successifs des fonctions qui auront lieu lors de son exécution. Ces appels se représentent facilement sous la forme d’un arbre. Ainsi le code suivant:

def a(x):
    y=c(x)
    z=b(y)
    retun z

def b(x):
    return c(x)+1

def c(x):
    return -x

a(12)

donne l’arbre d’appels suivant:

_images/arbre_appel.png

Les arbres d’appels de fonctions vous serviront lors de la phase de conception du projet pour imaginer et décrire le fonctionnement du logiciel avant d’avoir écrit le code.

  1. Dessiner un arbre d'appels

    Dessiner l’arbre d’appels de fonctions de la fonction principale main().

    def f1():
        f2()
        f3()
    def f2():
        pass
    def f3():
        f2()
    def main():
        f1()
    def f5():
        pass
    
    main()
    
    Votre réponse :

    Dessinez sur une feuille de papier.

    Une solution possible :
    _images/arbre_appel_corr.png

Redondance de code

Un bon codeur est un bon fainéant! Il veut absolument éviter de faire plusieurs fois le même travail pour rien. Pour atteindre cet objectif, il doit activement chasser les zone de code redondant.

La redondance de code, c’est quand on programme plusieurs fois la même séquence d’instruction ou qu’on définit plusieurs fois la même valeur dans un programme. Qui dit code redondant, dit au moins deux fois plus d’opérations pour écrire, maintenir et modifier le programme. La redondance de code introduit de sérieuses complications lors de la phase de débogage. La volonté d’optimiser les temps de calculs peut conduire à produire du code redondant, mais cela ne nous concerne pas ce semestre. Lorsque qu’on fait du copier/coller de code il est bon de se demander si on est pas en train d’introduire de la redondance. Ayez bien conscience qu’il n’y a aucun mal à programmer des fonctions très élémentaires. Ce n’est pas un problème d’avoir une fonction d’une seul ligne et ce n’est pas un problème d’avoir une multitude de fonctions dans un programme si cela permet d’éviter la redondance et de simplifier le code.

Les exercices/exemples suivants vous présentent 4 types de redondance:

  1. Redondance 1

    Pouvez-vous modifier le code pour éliminer les parties redondantes?

    Votre réponse :
    python : code_run362.py
    Sorties
    
                
    Commentaires

    Ne jamais écrire plusieurs fois la même valeur “en dure” dans le code. Privilégier l’utilisation d’une variable.

    Une solution possible :
    l=[]
    sizeL=10
    
    #initialisation de la liste
    for i in range(sizeL):
        l.append(i)
    
    #modification de la liste
    for j in range(sizeL):
        l[j]=l[j]+l[(j+1)%10]
        print l[j]
    
  2. Redondance 2

    Pouvez-vous modifier le code pour éliminer les parties redondantes?

    Votre réponse :
    python : code_run366.py
    Sorties
    
                
    Commentaires

    Utiliser des listes plutôt que des variables avec indices

    Une solution possible :
    def createGuardian():
        return {'position': (0,0) }
    
    guardians=[]
    
    #creation des gardes
    for i in range(3):
        guardians.append(createGuardian())
    
    #initialisation de leur positions
    for g in guardians:
        setRandomPosition(g)
    
  3. Redondance 3

    Pouvez-vous modifier le code pour éliminer la redondance?

    Votre réponse :
    python : code_run370.py
    Sorties
    
                
    Commentaires

    Quand deux fonctions ont des sections de code identiques, il peut être utile de factoriser ce code au moyen d’une troisième fonction.

    Une solution possible :
    #Code factorise
    def movePlayer(player,dt):
        print ("Le joueur se déplace")
        move(player,dt)
    
    def moveMonster(monster,dt):
        msg=monster["name"]+" se déplace."
        print (msg)
        move(monster,dt)
    
    def move(a,dt):
        vx,vy=a["speed"]
        x,y=a["position"]
        x=x+vx*dt
        y=y+vy*dt
        a["position"]=x,y
    
  4. Redondance 4

    Pouvez vous modifier le code pour éliminer la redondance?

    Votre réponse :
    python : code_run374.py
    Sorties
    
                
    Commentaires

    Quand on développe du code on évite de réinventer la roue. Il faut consulter la documentation pour et utiliser les fonctions natives du langage si elles existent.

    Une solution possible :
    liste=['a','b','c','d']
    
    #l operation index de python peut etre utilisee
    
    print liste.index('b')
    

Tests & Bugs

Un bug ou bogue, est une erreur de conception ou de codage qui conduit au dysfonctionnement d’un programme informatique. Aucun développeur, même très expérimenté, ne peut coder sans faire d’erreur. Souvent, la majeur partie du temps de développement est consacré à l’identification et à la correction des bugs. Voici une liste non exhaustive de bugs que vous rencontrerez:

  • Faute de frappe
  • Fuite mémoire
  • Erreur de condition d’arrêt d’une boucle (boucle infinie)
  • Division par 0
  • Dépassements de capacité d’une séquence
  • Dépassement d’un entier
  • Erreur d’initialisation d’une variable
  • Erreur dans le passage de paramètres
  • Interblocage

La recherche et la correction de bugs occupera la majeur partie de votre temps de développement lors du projet. Le meilleur moyen de ne pas passer trop de temps à déboguer, c’est d’éviter d’introduire des erreurs ou de les détecter très vites, au moment du développement de la section de code concernée. La rigueur que vous mettrez dans le respect des méthodes de développement, des règles d’écriture de code et dans les tests sera cruciale.

Les tests correspondent à la partie ascendante du cycle de développement en V.

_images/cycleV.png

Il existe plusieurs types de tests:

  • Tests unitaires : Sur partie d’un programme pour valider les fonctions indépendamment.
  • Tests d’integration : Lorsqu’on associe des parties de code déjà testées indépendamment.
  • Tests de validation : On teste l’adéquation entre les spécifcations du document de conception et le logiciel réalisé.
  • Recette : Acceptation du logiciel par la maîtrise d’œuvre
  • Tests de non régression : Permet de rejouer tous les tests à la suite d’un correctif ou d’une évolution.

Tester un module Python:

Exemple : test move

#code a tester
def movePlayer(player,dt):
    print ("Le joueur se déplace")
    move(player,dt)
def moveMonster(monster,dt):
    msg=monster["name"]+" se déplace."
    print (msg)
    move(monster,dt)
def move(a,dt):
    vx,vy=a["speed"]
    x,y=a["position"]
    x=x+vx*dt
    y=y+vy*dt
    a["position"]=x,y

#Test
if __name__ == '__main__':
    #test move
    a={"speed":(6,2),"position":(3,4)}
    move(a,0.5)
    if(a[position]!=(6,5)):
        print("move() test error")

    #test movePlayer
    movePlayer(a,0.5)
    if(a[position]!=(6,5)):
        print("movePlayer() error")

    #test moveMonster
    a["name"]="bob"
    moveMonster(a,0.5)
    if(a[position]!=(6,5)):
        print("moveMonster() error")

Tester le code suivant
Votre réponse :
python : code_run379.py
Sorties

            
Une solution possible :
#Level.py

def create(width=5, height=5):
    level={'height':height,'width':width}
    level['grid']=[[' ' for x in xrange(height)] for x in xrange(width)]
    return level

def addWall(l,x,y):
    l['grid'][x][y]='*'

def isEmpty(l,x,y):
    #teste si il y a un mur dans la case
    if l['grid'][x][y]==' ':
        return True
    else:
        return False

if __name__=='__main__':
    level=create(5,5)
    if isEmpty(level,3,2)==False:
        print 'la case devrait etre vide'
    else:
        addWall(level,3,2)
        if isEmpty(level,3,2):
            print 'la case ne devrait pas etre vide'
        else:
            print 'Level.py test ok'

Découpage en modules

En python, un module est un fichier qui contient un programme python. Le nom du fichier est le nom du module suivi de l’extension .py. Quand la taille d’un programme devient conséquente, il est nécessaire de découper ce programme en plusieurs modules pour les raisons suivantes:

  • Lisibilité: En regroupant certaines parties de code dans des modules cohérents, il est plus facile de lire et donc de comprendre un programme.
  • Modularité: Notamment lorsqu’on travail en équipe, il est intéressant de concevoir des parties de programme plus ou moins indépendantes qu’on peut développer, modifier et remplacer plus facilement.
  • Ré-utilisabilité: un module bien fait peut devenir une bibliothèque qu’on pourra réutiliser à l’occasion de futur projets.

Les modules s’importent les uns les autres en utilisant l’instruction import module Lorsqu’un module A importe un module B, le module A peut utiliser les fonctions de B. En contrepartie, A devient dépendant de B. En effet, si B est modifié ou supprimé, cela peut entraîner un dysfonctionnement du module A.

Pour bien comprendre les relations de dépendance entre les modules d’un programme, il est nécessaire de savoir dessiner un graphe de dépendances entre les modules. Ici, un programme principal importe 4 modules :

_images/graphe_modules.png
L’étude du graphe de dépendances nous permet de réfléchir à l’architecture du programme. Deux types de dépendance doivent être évitées:
  1. les références mutuelles
  2. les dépendances circulaires

Lors de la phase de conception d’un logiciel on cherchera à imaginer le découpage du logiciel en modules les plus indépendants possible, en évitant les références circulaires.

_images/graphe_modules2.png

Le graphe ci-dessus illustre des dépendances probablement mal conçues entre différents modules.

  1. Graphe de dépendance du programme ``animat``.

    Reprendre l’exemple de code donné au début de ce cours pour un produire un graphe de dépendances entres ses différents modules. ExempleAnimat.tgz ou ExempleAnimat.zip

    Une solution possible :
    _images/graphe_modules_animat.png

Barrière d’abstraction

Principe

Le découpage d’un programme en modules ne doit pas se faire de façon aléatoire. Il faut bien y réfléchir, car cela définit l’architecture du projet. Le principe architecturale qui vous est imposé pour le projet, repose sur le principe de la mise en œuvre de barrière d’abstraction et l’utilisation de types abstraits de données.

Remarque
Il existe plusieurs méthodes comparables pour concevoir un programme procédural, mais pour ce projet, une méthode vous est imposée. Le fait d’imposer cette méthode de développement est discutable mais nous n’avons pas le temps d’aborder les différentes façons de faire. Le choix de la barrière d’abstraction a été fait en accord avec le reste du programme pédagogique de l’école. Si vous avez déjà pris d’autres habitudes de programmation, il faudra faire l’effort de vous adapter au style de programmation proposé. Cela ne veut pas forcément dire que vos habitudes sont mauvaises mais simplement qu’elles sont différentes. Un bon développeur doit savoir adapter ses méthodes de développement au contexte et parfois maîtriser plusieurs styles. Ainsi, même si vous pensez être à l’aise en informatique, prêtez bien attention aux règles de programmation préconisées dans ce cours, pour la réalisation de votre projet.

Le principe de la mise en œuvre de la barrière d’abstraction repose sur l’identification de types abstraits de données et permet le découpage du logiciel en modules indépendants. Ce principe n’est absolument pas imposé par le langage python, il s’agit d’un élément de méthodologie. Une fois ce découpage réalisé, la répartition des fonctions à travers les différents modules devient logique, favorisant ainsi l’homogénéité du code et le travail en équipe.

La barrière d’abstraction permet de cacher dans un module les détails d’implémentation des services rendus par ce module. Pour utiliser une fonction, il n’y a pas besoin de savoir comment elle est codée. Il suffit de connaître les spécifications qui fournissent une définition abstraite du service rendu. De plus, pour manipuler des données complexes à l’aide de fonctions, il n’est pas toujours nécessaire de savoir comment ces données sont structurées. C’est là qu’intervient la notion de type abstrait.

Définition : Type abstrait de données.

  1. Type : Un type abstrait de données est d’abord un type. Nous décidons de considérer qu’une telle structure de données utilisée dans le programme correspond à un nouveau type de donnée qui n’existe pas dans python. Par exemple, imaginons qu’un casse brique manipule des dictionnaires pour représenter la ou les balles du jeu :

ball= {'position':(5.0,0.0), 'speed':(0.0,0.0), 'radius'='1', 'state':'normal' }.

bonusBall= {'position':(0.0,0.0), 'speed':(0.0,1.0), 'radius'='1', 'state':'fast' }.

Nous pouvons décider de créer un nouveau type de données: Ball; en indiquant qu’une donnée de ce type est un dictionnaire comprenant des entrées pour les clés suivantes : 'position', 'speed', 'radius' et 'state'. La position et la vitesse sont des tuple de 2 réels. Le rayon est un entier. L’état est une chaîne de caractère.

2. Abstrait : On qualifie les types d’abstraits car on veut pouvoir manipuler les données sans se préoccuper de la manière dont elle sont codées. Par exemple, lorsque nous manipulons une donnée de type Ball avec une fonction move(ball), peu nous importe de savoir si la balle est une liste, un dictionnaire ou autre chose, tant que la fonction move qui prend en paramètre une donnée de type Ball remplit sont rôle et modifie la position de la balle. Dans cet exemple, “abstrait” signifie donc que je sais que je manipule une structure de type Ball; que je sais que cette structure contient des informations relatives à sa vitesse, sa position, son état et son rayon; mais que je ne sais pas comment elle est construite dans le détail:

Type : Ball = struct
        position : float,float
        speed : float,float
        radius : int
        state : str

Attention la définition du type ci-dessus n’est pas du code python. Il s’agit de ce qu’on souhaite trouver dans le document de conception. Il faudra être suffisamment rigoureux pour coder en accord avec ce qui aura été conçu, en respectant notamment scupuleusement le nommage des données.

Pour mettre en œuvre la barrière d’abstraction, il faudra être capable d’identifier des types de données. Puis, il suffira de créer un module par type de données. L’exemple suivant montre comment créer un module à partir des données utilisées pour créer des graphes de ville.

Exemple : ``CintyInfo``

Type : CityInfo = struct
    name : str
    population : int

Pour construire le module correspondant au type cityInfo:

  1. Regrouper dans un module toutes les fonctions qui manipulent la même donnée : CityInfo.py. Déclarer le type CityInfo

    class CityInfo : pass
    
  2. Constructeur: Définir une fonction pour créer la donnée:
    def create(name='',population=0):
        cityInfo=CityInfo()
        cityInfo.name=name
        cityInfo.population=population
        return cityInfo
    
  3. Accesseurs: Proposer des accesseurs pour retrouver des informations décrites par la donnée. On utilise le mot clé get:
    def get_name(cityInfo):
        return cityInfo.name
    def get_population(cityInfo):
        return cityInfo.population
    
  4. Mutateurs: Proposer des mutateurs pour modifier la donnée. On utilise le mot clé set.
    def set_name(cityInfo, name):
        cityInfo.name=name
    def set_population(cityInfo,number):
        cityInfo.population=number
    
  5. Opérations: Coder l’ensembles des fonctions permettant de réaliser une opération sur les données.
    def show(cityInfo):
        msg= "La ville de"+ cityInfo.name+" comporte "
        msg=msg+ cityInfo.population+ " habitants"
        print(msg)
    
  6. Tests: Mettre en œuvre les tests unitaires permettant de s’assurer du bon fonctionnement du module.
    #tests
    if __name__=="__main__":
        #verification par jeu de test, comparaison visuelle, etc...
        ci=create()
        show(ci)
    

Exemple : Le type ``Ball``

Le type Ball

Voici un autre exemple pour le type Ball:

#Ball.py
class Ball: pass

#constructeur
def create(radius):
    b=Ball()
    b.position=(0.0,0.0)
    b.speed=(0.0,0.0)
    b.radius=radius
    b.state='normal'
    return b

#accesseurs
def get_position(ball):
    return ball.position

def get_speed(ball):
    return ball.speed

def get_radius(ball):
    return ball.radius

def get_state(ball):
    return ball.state

#mutateurs
def set_position(ball, x, y):
    ball.position=(x,y)

def set_speed(ball,vx,vy):
    ball.speed=(vx,vy)

def set_radius(ball,radius):
    ball.radius=radius

def set_state(ball, state):
    ball.state=state


def move(ball, dt):
    # deplacement de la balle sur un pas de temps dt
    #... a implementer...

def stop(ball):
    ball.speed=(0,0)

#tests
if __name__=="__main__":
    #verification par comparaison visuelle
    my_ball=create(2)
    setSpeed(my_ball,2,4)
    print get_position(my_ball)
    move(my_ball, 10)
    print get_position(my_ball)

Règles de codage

  1. Module: En dehors du programme principal Main.py (et sauf cas très particuliers), chacun des modules que vous concevrez devra correspondre à un type abstrait de données. Le module portera le nom du type. On fait toujours commencer le nom d’un type par une lettre majuscule, cela permet de différencier les variables des types. Le nom du module commence donc par une majuscule.

  2. Constructeur: Au début d’un module on trouve le (ou les) constructeur(s) de la donnée. Un constructeur est une fonction qui capable de fabriquer la donnée.

    #AbstractType.py
    class AbstractType: pass
    
    
    #constructeur
    
    def create(parametres):
    
        #creation
        data=AbstractType()
    
        #initialisation en fonction des paramètres
        ...
    
        #retour de la donnee
        return data
    

A l’extérieur du module, on pourra appeler le constructeur de la manière suivante :

import AbstractType

d= AbstractType.create(...)

On s’obligera à toujours utiliser un constructeur et ne jamais créer soit même la donnée.

  1. Accesseurs & Mutateurs: Ce sont des fonctions très simples qui permettent d’accéder au contenu des données. A l’occasion de ce cours, elle doivent être écrites de manière systématique pour chaque information définie par le type. Ces fonctions fournissent un outil de débogage très important, dès lors qu’on se force à les utiliser systématiquement pour manipuler les données.

    #AbstractType.py
    
    #accesseurs/mutateurs
    
    def get_x(data):
    
        #recuperation de x dans data
        x=...
    
        return x
    

A l’extérieur du module, on appellera l’accesseur et le mutateur :

import AbstractType

d= AbstractType.create(...)

#reinitialisation de la valeur avec le mutateur
AbstractType.setX(d,'12')

#recuperation de la valeur avec accesseur
x=AbstractType.getX(d)

4. Opérations: On regroupe dans le module toutes les fonctions qui manipulent le type de données correspondant. Le premier paramètre d’une opération est en général une donnée du type correspondant. La difficulté est de penser les fonctions pour qu’elles ne manipulent qu’un type à la fois. Il est souvent utile de consulter le graphe de dépendance des modules pour concevoir correctement les fonctions. Parfois il faut décomposer une fonction à travers les modules pour obtenir un résultat pertinent. Ce point sera abordé dans la partie conception.

#AbstractType.py

#operations

def operation(data):

    #action a realiser sur la donnee
    data...

    #renvoie eventuel d une valeur
    return ...

A l’extérieur du module, on appellera la fonction :

import AbstractType

d= AbstractType.create(...)

#realisation d une operation:
AbstractType.operation(d)
  1. Respect de la barrière d’abstraction: Respecter la barrière d’abstraction, c’est s’obliger à toujours manipuler une donnée avec des opérations fournies par le module correspondant. En dehors du code du module, il ne faut jamais modifier ou lire directement une donnée :

    import Ball
    
    b= Ball.create(5)
    
    #ce qu il ne faut pas ecrire
    print('radius=', b.radius)
    
    #ce qu il faut ecrire
    print 'radius=', Ball.getRadius(b)
    

Même si cela vous paraît fastidieux, il vous est fortement conseillé de respecter la barrière d’abstraction dès le début du codage. Cette règle est un garde-fou qui empêche de commettre des erreurs de conception. L’expérience montre qu’à chaque fois qu’un projet n’a pas respecté la règle, il n’a pu aboutir que dans la “douleur”. Si on se rappelle qu’un développeur passe la majeur partie de son temps à déboguer, le temps perdu à mieux coder n’est pas perdu...

  1. Écrire le module ``Player.py`
    • Soit le type suivant

      Type : Player = struct
          position : int
          name : str
          items : list d'Item
      
    • Une opération move incrémente la position du joueur de 1

    • Une opération add_item ajout un Item dans la liste d’items.

    • Le type Item est implémenté par module Item.py

    Votre réponse :
    python : code_run389.py
    Sorties
    
                
    Une solution possible :
    #Player.py
    class Player: pass
    
    def create(name="noname",position=0):
        p=Player()
        p.name=name
        p.position=position
        p.items=[]
        return p
    
    def get_name(p):
        return p.name
    def get_position(p):
        return p.position
    def get_items(p):
        return p.items
    #on peut aussi ajouter cet accesseur
    def get_item(p,i):
        if i >0 and i<len(p['items']):
            return p.items[i]
        else return None
    
    def set_name(p,name):
        p.name=name
    def set_position(p,position):
        p.position=position
    def set_items(p,items):
        p.items=items
    
    def move(p):
        p.position=p.position+1
    
    def add_item(p,item):
        p.items.append(item)
    
    if __name__=='__main__':
      '''ici les tests'''
    
structurer un programme déjà écrit

Il est préférable de réaliser cet exercice en dehors de l’ENIBOOK.

#pasdemodule.py
#code moche sans module
cities=None
def init():
	global cities
	#graphe
	info={'name':'Brest','nbSchool':7,'population':141000}
	s1={'info':info,'roads':[]}
	info={'name':'Chateaulin','nbSchool':1,'population':5000}	
	s2={'info':info,'roads':[]}
	info={'name':'Quimper','nbSchool':5,'population':90000}	
	s3={'info':info,'roads':[]}
	info={'name':'Landivisiau','nbSchool':1,'population':10000}
	s4={'info':info,'roads':[]}
	info={'name':'Landerneau','nbSchool':0,'population':15000}
	s5={'info':info,'roads':[]}

	cities=[s1,s2,s3,s4,s5]
	addRoad(s1,s2,46)
	addRoad(s1,s5,26)
	addRoad(s2,s3,28)
	addRoad(s2,s5,36)
	addRoad(s4,s5,17)
	
def addRoad(v1,v2,km):
	v1['roads'].append((v2,km))
	v2['roads'].append((v1,km))	
	
def getDistance(v1,v2):
	#return 0 if no way
	distance =0
	for link in v1['roads']:
		v,km=link
		if v==v2:
			break
	return distance

def askCity():
	global cities
	name=raw_input('Choisissez un nom de ville')
	for v in cities:
		if v['info']['name']==name:
			showInfo(v['info'])
			return
	print 'Pas de ville de ce nom' 

def showInfo(i):
	msg='La ville de '+i['name']+' a une popuation de '+str(i['population'])+' habitants.'
	print msg
	
def main():
	init()
	askCity()

if __name__ == '__main__':
	main()
	
Votre réponse :
python : code_run393.py
Sorties

            
python : code_run394.py
Sorties

            
python : code_run395.py
Sorties

            
Une solution possible :
#main.py
import Cities

cities=None

#ne pas confondre cities et Cities, respectivement la reference vers la donnee et le module qui decrit le type abstrait.


def init():
	global cities
	#graphe
	my_cities=Cities.create()
	Cities.add_city(cities,'Brest',7,141000)
	Cities.add_city(cities,'Chateaulin',1,5000)
	Cities.add_city(cities,'Quimper',5,90000)
	Cities.add_city(cities,'Landivisiau',1,10000)
	Cities.add_city(cities,'Landerneau',0,15000)

	Cities.add_road(cities,'Brest','Chateaulin',46)
	Cities.add_road(cities,'Brest','Landerneau',26)
	Cities.add_road(cities,'Chateaulin','Quimper',28)
	Cities.add_road(cities,'Chateaulin','Landerneau',36)
	Cities.add_road(cities,'Landivisiau','Landerneau',17)
	
def ask_city():
	global cities
	name=raw_input('Choisissez un nom de ville')
	Cities.show_city_info(cities,name)


def main():
	init()
	ask_city()

if __name__ == '__main__':
	main()
	
#cityInfo

class CityInfo: pass

def create(nom,nb_school,population):
	info=CityInfo()
	info.name=nom
	info.nb_school=nb_school
	info.population=population
	return info

def get_name(i):
	return i['name']

def set_name(i,name):
	i['name']=name
	return

def get_population(i):
	return i['population']

def set_population(i,population):
	i['population']=population
	return

def get_nb_school(i):
	return i['nb_school']

def set_nb_school(i,nb):
	i['nb_school']=nb
	return
	
def show(i):
	msg='La ville de '+i['name']+' a une popuation de '+str(i['population'])+' habitants.'
	print msg
	return
if __name__=='__main__':
	'''ajouter les tests unitaires ici'''
#cities.py

#on importe le module que Cities utilisera 
import CityInfo

def create():
	#ici, on decide de code le type abstarait au moyen d'un liste... pourquoi pas! 
	return []

def add_city(cities,nom,nb_school,population):
	#on appelle le construction du module CityInfo
	info=CityInfo.create(nom,nb_school,population)
	c={'info':info,'roads':[]}
	cities.append(c)

def add_road(cities,name_1,name_2,km):
	#trouver les sommet
	
	s_1=find_city(cities,name_1)		
	s_2=find_city(cities,name_2)		
	
	#ajouter liens
	s_1['roads'].append((s_2,km))
	s_2['roads'].append((s_1,km))	
	
def get_distance(name_1,name_2):
	#return 0 if no way
	distance =0
	v_1=find_city(cities,name_1)		
	v_2=find_city(cities,name_2)		

	for link in v_1['roads']:
		v,km=link
		if v==v_2:
			distance=km
			break
			
	return distance

def find_city(cities,name):
	city=None
	for sommet in cities:
		#pour acceder au contenu de city en respectant la barrière d'abtraction, on utilise un accesseur
		if CityInfo.get_name(sommet['info'])==name:
			city=sommet		
	return city
	
def show_city_info(cities,name):
	for v in cities:
		if CityInfo.get_name(v['info'])==name:
			CityInfo.show(v['info'])
			return
	print 'Pas de ville de ce nom' 


if __name__=='__main__':
	'''ajouter les tests unitaires ici'''