Bienvenue sur IndexError.

Ici vous pouvez poser des questions sur Python et le Framework Django.

Mais aussi sur les technos front comme React, Angular, Typescript et Javascript en général.

Consultez la FAQ pour améliorer vos chances d'avoir des réponses à vos questions.

Questions de méthodologie sur les classes et de noms d'objets 'variables'.

+3 votes

[Python 3.4 - Linux Mint/Windows 7]

===== Préambule
Questions de méthodologie à base de classes et de noms d'objets
'variables'.

Je cherche à savoir deux choses :
- quelle est la méthodologie quand on fait face à des objets qui contiennent d'autres objets qui contiennent eux-mêmes encore des objets --> Classes ? Justement pas de classes ? Autre ? je cherche à savoir quels grands axes de méthodes sont les plus efficaces pour ce genre de projets.
- on a une liste non définie au préalable et variable d'objets (et donc, d'objets contenant des objets qui contiennent eux-mêmes des objets, le tout en nombre variable et inconnu à la base) : comment instancier ces objets ? La méthode -- la façon de faire -- m'est inconnue et me donne mal à la tête :-)

Soit un fichier donnant diverses informations qui est présenté plus bas.
Il s'agit, mettons, d'un fichier de "configuration" par an des véhicules
(IDENTIFIANT) attachés à une VILLE. Il liste également les accessoires
(NOM_EQ) par véhicule et les caractéristiques de ces VILLES.

Les données sont de type CSV et toutes dans le même fichier --> fichier multi-csv (mais peu
importe, je saurai faire le brutal et découper mon fichier en autant de CSV simple que nécessaire).

Je vais produire une interface graphique qui représentera ceci, avec des onglets :

| VILLES | ORIGINE VEHICULE | CX_01 | CX_02 | Clio_01| .. | etc. |

Ca c'était pour présenter un peu.

(à partir de là, je pense qu'il faut regarder la composition du fichier plus bas, après les questions)

====== QUESTIONS ======

Le nombre de donnée par catégories (VILLES, IDENTIFIANT, NOM_EQ) est
variable. Mettons que ce soit un fichier différent par an, ou un fichier
de ce type par pays. Et c'est cette variabilité qui me tord le cerveau.

1) pensez-vous que je dois programmer ce découpage en utilisant des
classes ?

2) Dans ce cas, dois-je faire une classe "VILLE", une classe
"ORIGINE_VEHIC", une classe "VEHICULE" et une classe "EQUIPEMENT" ?

Sachant que chaque catégorie est fixe dans le nombre d'élément qui la
décrit : une VILLE sera toujours représentée par Actif, ville, nbre_auto,
code_postal et en_repar.
Un équipement sera toujours décrit par sa couleur, par le véhicule dans lequel
il est inséré, par son nom et s'il est actif ou non.
Seul le nombre d'éléments de chaque catégorie est inconnu et variable, pas
leur composition.

Ca me donnerait (enfin, je crois) :

class VILLES():
    def __init__(self, Actif, ville, nbre_auto, code_postal, en_repar):
        self.Actif = Actif
        self.ville = ville
        self.nbre_auto = nbre_auto
        self.code_postal = code_postal
        self.en_repar = en_repar

class ORIGINE_VEHIC():
    def __init__(self, Actif, ville, ID, num1):
        self.Actif = Actif
        self.ville = ville
        self.ID = ID
        self.num1 = num1

class VEHIC():
    def __init__(self, Actif, ID, nom_Eq, couleur):
        self.Actif = Actif
        self.ID = ID
        self.nom_Eq = nom_Eq
        self.couleur = couleur)

class Nom_Eq():
    ... je ne mets pas la suite ...

puis pour chaque classe, des méthodes qui exploitent les données, ce qui est hors-propos ici.

3) faut-il que mes classes soient "filles" des autres ? Faut-il que
VEHIC soit fille de ORIGINE_VEHIC elle-même fille de VILLE ?
Que Nom_Eq soit fille de VEHIC ? Puisque finalement mes équipements
sont dans mes véhicules qui sont dans des villes (oui, avec Origine
Vehic je ne sais pas trop quoi penser, mais je m'en débrouillerai).

4) puisque mon nombre d'objets n'est pas connu à la base, comment les
nommer/définir/instancier ?
Je sais faire un :

ma_Clio_01 = VEHIC()

pour instancier VEHIC à un nom que j'aurais défini moi-même. Mais puisque
je ne connais pas le nom des véhicule à priori, il faudra que je décide
de les nommer d'une manière ou d'une autre. Je pensais pouvoir utiliser
son IDENTIFIANT pour instancier sa classe.

Donc comment je fais pour coller au nom indiqué par mon fichier, par
exemple "Clio_01" le préfixe "ma_" qui me donnera le nom auquel je vais
instancier ma classe ?

Puis-je (et, est-ce bien ?) utiliser un dictionnaire dans lequel je
mettrais (attention, ça pique):

mon_dict = {"Clio_01": "0001", "Clio_02": "0002", "CX_01": "0003" ...}

(Le "0001" serait créer automatiquement à partir d'un compteur ou d'un
itérateur)

pour pouvoir faire plus tard un:

mon_dict['Clio_01'] = VEHIC()

(je vous l'avais dit : ça pique les yeux). Existe-t-il une manière pour
ne pas mettre "0001", une chaîne, qui me semble en faire rire plus d'un
tellement cette syntaxe montre à quel point je suis noob ?
Existe-t-il mais un objet d'un type inconnu de moi qui permettrait ça ?
Ce qui donnerait :

mon_dict = {"Clio_01": Pouet01, "Clio_02": Pouet02, ...}

Et sur ces "Pouet" je pourrais instancier ma classe ?

Ou, on abandonne les dictionnaires et y'avait plus simple ?

=== Description du fichier ===

un même "mot" de référence, ex: "IDENTIFIANT" représente la même chose
pour toutes les tables.

pour chaque ville, décrit si l'officine est en activité (Y=yes), le
nbre d'automobiles affectées à cette ville, le département et le
nbre d'automobiles en cours de réparation.
Ainsi, l'officine de Lille est active, elle compte 5 véhicules dont
1 est en réparation. Le dpt est 59.

!! le nombre de VILLE n'est pas connu et est variable !!

ACTIF;VILLE;NBRE_AUTOMOBILES;CODEPOST;EN_REPAR
Y;LILLE;5;59;1
Y;PARIS;1;75;0

Décrit la liste des automobiles affectées à une ville. Chaque véhicule
a un identifiant unique. Le NUM1 importe peu (c'est pour dire
qu'il y a d'autres données à traiter). Le véhicule peut être actif ou
non.
A Lille on trouve les 5 véhicules nommés CX01, CX02, ..., Scenic01.
A Paris, il n'y a que Scenic
02. Le nom ne peut pas être "Scenic_01"
car ce nom doit être unique, toutes villes confondues.

!! Le nombre de véhicule (IDENTIFIANT) n'est pas connu et variable !!

ACTIF;VILLE;IDENTIFIANT;NUM1
Y;LILLE;CX_01;2
Y;LILLE;CX_02;1
Y;LILLE;Clio_01;4
Y;LILLE;Clio_02;3
Y;LILLE;Scenic_01;5
Y;PARIS;Scenic_02;2

Pour le véhicule CX_02, on a des items (siège, carrosserie, etc.)
dont on donne la couleur. Mais je ne rentre pas dans les détails, ce
n'est pas le but.

!! Le nombre d'item (NOM_EQ) pour un véhicule donné (IDENTIFIANT) n'est
pas connu et variable !!

ACTIF;IDENTIFIANT;NOM_EQ;COULEUR
Y;CX_02;Siege_avant;bleu
Y;CX_02;Siege_arriere;vert
Y;CX_02;carrosserie;rouge

Idem Clio02 et idem TOUS les véhicules : j'aurais des données pour
chaque véhicule. Le nombre de données est VARIABLE même pour 2
Véhicules similaires (Clio
01, Clio_02).

ACTIF;IDENTIFIANT;NOM_EQ;COULEUR
Y;Clio_02;Siege_avant;bleu_des_mers_du_sud
Y;Clio_02;Pedales;noir_mais_alors_vraiment_noir
Y;Clio_02;carrosserie;rose_pipi
demandé 5-Avr-2015 par Agagax

ça fait beaucoup d'informations et de questions, c'est un peu difficile de te répondre ! Est-ce que tu peux en dire un peu plus sur ton programme : tu dois juste charger ces données, les lier entre elles et les afficher à l'utilisateur ?

J'approuve eliotberriot, tu ferais mieux de fragmenter ton problème et d'ouvrir un post / question, ce serait plus gérable pour les lecteurs, plus organisé pour toi :D.
En général, écrire des pavés ça ne motive pas tellement les lecteurs x), de plus ça permettra à ceux qui veulent répondre ne de répondre que sur ce qu'ils maîtrisent, donc peut-être qu'une partie de ton problème.
/Kiss

1 Réponse

+9 votes
 
Meilleure réponse

1) pensez-vous que je dois programmer ce découpage en utilisant des classes ?

Définitivement oui. Vu la complexité du problème, ça me parait indispensable.

2) Dans ce cas, dois-je faire une classe "VILLE", une classe "ORIGINE_VEHIC", une classe "VEHICULE" et une classe "EQUIPEMENT" ?

Également oui. Il s'agit d'entités bien distinctes, donc une classe par type d'entité.

3) faut-il que mes classes soient "filles" des autres ?

Non, car une ville et un véhicule n'ont rien à voir.

Par contre, si certaines entités ont des comportements en commun, tu peux utiliser des mixins pour les implémenter de façon propre.

Par exemple, si les équipements et les véhicules ont tous les deux une couleur (et, plus tard, des méthodes associées à cette couleur), tu peux faire:

class CouleurMixin(object):
    def __init__(self, *args, **kwargs):
        self.couleur = kwargs['couleur']

    def afficher_couleur(self):
        print('Ma couleur est {0}'.format(self.couleur))

class Vehicule(CouleurMixin):
    pass

class Equipement(CouleurMixin):
    pass

4) puisque mon nombre d'objets n'est pas connu à la base, comment les nommer/définir/instancier ?

À mon avis, tu peux procéder en deux étapes :

  1. D'abord, récupérer les données de tes CSV, les nettoyer si nécessaire, et les stocker dans un itérable, comme une liste
  2. Ensuite, boucler sur cet itérable, et instancier tes objets au fur et à mesure à partir des données du CSV.

Tu vas devoir commencer par les entités qui sont complétement indépendantes (qui n'ont de lien avec aucune autre entité). Les villes me semblent un bon point de départ:

import csv

class Ville(object):
    def __init__(self, identifiant, actif, nombre_vehicules, code_postal, nombre_vehicules_en_reparation):
        self.identifiant = identifiant
        self.actif = actif
        self.nombre_vehicules = nombre_vehicules
        self.code_postal = code_postal
        self.nombre_vehicules_en_reparation = nombre_vehicules_en_reparation



def recuperer_donnees_villes():
    """On parse le fichier CSV et on retourne les données propres"""
    donnees = []
    with open('villes.csv') as f:
        reader = csv.reader(f, delimiter=';')
        for row in in reader:
            donnees_ville = {}
            donnees_ville['actif'] = True if row[0] == 'Y' else False
            donnees_ville['identifiant'] = row[1]
            donnees_ville['nombre_vehicules'] = int(row[2])
            donnees_ville['code_postal'] = row[3]
            donnees_ville['nombre_vehicules_en_reparation'] = int(row[4])
            donnees.append(donnees_ville)
    return donnees

def creer_villes(donnees):
    """On boucle sur les données, on instancie les villes, et on stocke chaque instance dans un dictionnaire
    avec l'identifiant de la ville comme clé"""

    villes = {}
    for donnee in donnees:
        ville = Ville(**donnees) # Voir l'article de sam et max sur l'unpacking si nécessaire
        villes[ville.identifiant] = ville

    return villes

Il est important de bien séparer l'étape de retraitement des données de l'instanciation des classes. C'est plus facile à tester, et à maintenir si la structure de tes fichiers csv change.

Ensuite, si on applique la même logique aux véhicules:

class Vehicule(object):
    def __init__(self, identifiant, actif, ville, num1):
        self.identifiant = identifiant
        self.actif = actif
        self.ville = ville
        self.num1 = num1


def recuperer_donnees_vehicules(villes):
    """Ici, on passe en argument le dictionnaire contenant la liste des instances des villes"""
    donnees = []
    with open('vehicules.csv') as f:
        reader = csv.reader(f, delimiter=';')
        for row in in reader:
            donnees_vehicule = {}
            donnees_vehicule['actif'] = True if row[0] == 'Y' else False
            donnees_vehicule['ville'] = villes[row[1]] # on récupère l'instance ville correspondante
            donnees_vehicule['identifiant'] = row[2]
            donnees_vehicule['num1'] = row[3]
            donnees.append(donnees_vehicule)
    return donnees

def creer_vehicules(donnees):
    """On boucle sur les données, on instancie les villes, et on stocke chaque instance dans un dictionnaire
    avec l'identifiant de la ville comme clé"""

    vehicules = {}
    for donnee in donnees:
        vehicule = Vehicule(**donnees) # Voir l'article de sam et max sur l'unpacking si nécessaire
        vehicules[vehicule.identifiant] = vehicule

    return vehicules

Si on écrit une petite fonction pour faire marcher tout ça, on obtient:

def recuperer_toutes_les_instances():
    donnees_villes = recuperer_donnees_villes()
    villes = creer_villes(donnees_villes)

    donnees_vehicules = recuperer_donnees_vehicules(villes)
    vehicules = creer_vehicules(donnees_vehicules)

    # je n'a pas écrit le code pour les équipements, mais ça fonctionne pareil que les véhicules,
    # sauf qu'au lieu de passer les villes, on passe le dictionnaire contenant les véhicules
    # pour pouvoir associer les équipements au véhicule

    donnees_equipements = recuperer_donnees_equipements(vehicules)
    equipements = creer_equipements(donnees_equipements)

    return villes, vehicules, equipements

Voila, j'espère que ça pourra te mettre sur la voie. À mon avis, tu aurais probablement intêret à utiliser une base de données et un ORM pour gérer tout ça. Parce que charger tout à chaque fois au lancement du programme, ça risque de prendre du temps si tes fichiers CSV grossissent.

répondu 5-Avr-2015 par eliotberriot (678 points)
sélectionné 8-Avr-2015 par Sam

+1 Pour la base de données, en effet la composition des données prêtent vraiment à l'utilisation d'un systeme de gestion de base de données.
Par exemple :
Une table une VILLE avec pour "attributs" : Actif, ville, nbreauto,
code
postal.
Pareil pour VEHICULE, avec tous ses attributs, et une clé primaire NumVéhicule qui sera unique pour chaque véhicule et une clé étrangère equipement qui fera référence à une autre table avec pour attrbuts cette fois :
le véhicule attribué , par son nom et son activité (actif ou non)
En effet, le SGBD t'évitera de réinventer la roue, de ce fait tu pourrais utiliser des fonctionnalitées
de base présentes dans le SGBD, comme mettre à jour sa base de données, faire des séléctions selon tel et tel
critère, faire des jointures, des requêtes conditionnelles, tout cela géré par un moteur SQL par ex, je peux t'assurer
que tu gagneras beaucoup de temps, plutôt que de tout réimplanter toi-même^^.
/Kiss

@Eliotberriot: vraiment merci pour tout ça. Il faut que je regarde et que je digère mais je vois mieux vers quoi je peux aller. Franchement mieux, même.
J'aurai dû découper mes questions en plusieurs parties (plusieurs 'posts'), je pense.
Euh ... dernier truc, comment je fais pour valider la réponse ? Je ne vois pas de bouton ... Peut-être est-ce parce que je n'étais pas encore enregistré quand j'ai posé cette question ?

@Agagax, content d'avoir pu t'aider. Pour la validation de la réponse, effectivement, il semble que tu aie posté ta question sans être inscrit, du coup je ne sais pas du tout comment ça se passe !

@Eliotberriot: j'ai envoyé un commentaire/courriel depuis la page "FAQ" pour savoir comment valider la réponse.

@boblinux: merci pour le commentaire. Quoiqu'interressante, la base de données ne sera pas justifiée. En effet, l'application devra être aussi légère que possible. Il s'agit d'une interface graphique permettant de visualiser (et, à terme, de modifier) un fichier de configuration multi-csv. Cette configuration se fait actuellement avec un éditeur de texte mais comme je connais l'aversion atavique de l'humain face au fichier texte voire de la ligne de commande, d'une part, et pour ma propre formation à Python, d'autre part, j'ai décidé de faire cette interface. Cette interface devra être lancée, typiquement, d'une clé USB ou copiée sur le PC depuis lequel on aura à faire la configuration. Comme il s'agira de postes client n'ayant pas forcément de MySQL ou équivalent et sur lesquels cette installation serait interdite, la solution SGBD n'est pas adéquate (bon, et puis, il est vrai, je n'y connais rien en MySQL et consort ce qui rallongerait le temps de développement de deux ou trois millénaires ^^). Je note toutefois tes infos (clés primaires, secondaires, ...) parce qu'à un moment donné, je ne sais pas pourquoi, mais je pense que je mettrai mes mains dans le camboui "base de données".

Pour info, juste comme ça, ce fichier multi-csv est la configuration d'un appareil de type "passerelle de communication"" entre des équipements causant le Modbus dans le texte (mes "Véhicules") et des PC/SCADA jactant le IEC 60870-5-101 (mes "villes"). Chaque équipements traitent des informations d'entrée, de sortie ou des commandes (mes "accessoires").

En tout cas, mille mercis.

@Agagax, sans aller jusqu'à du MySQL, tu as dans la bibliothèque standard de python le module sqlite3 qui t'offre les possibilités d'une base de données sans la lourdeur de mise en place d'un serveur de base de données. Les bases sqlite sont de simples fichiers, donc stockables sans problème sur clé USB.

Réponse validée par les admins :) Et upvotée. Et saluée. Parce que wow.

...