web-dev-qa-db-fra.com

Comment concevoir une classe en Python?

J'ai eu une aide vraiment impressionnante sur mes questions précédentes pour détecter les pattes et orteils dans une patte , mais toutes ces solutions ne fonctionnent que pour une mesure à la fois.

maintenant j'ai des données qui consiste en:

  • environ 30 chiens;
  • chacun a 24 mesures (divisées en plusieurs sous-groupes);
  • chaque mesure a au moins 4 contacts (un pour chaque patte) et
    • chaque contact est divisé en 5 parties et
    • a plusieurs paramètres, comme le temps de contact, l'emplacement, la force totale, etc.

alt text

De toute évidence, tout coller dans un seul et même objet volumineux ne le réduira pas. Je me suis donc dit que je devais utiliser des classes au lieu des nombreuses fonctions actuelles. Mais même si j'ai lu le chapitre de Learning Python sur les classes, je ne parviens pas à l'appliquer à mon propre code ( lien GitHub )

J'ai aussi l'impression qu'il est plutôt étrange de traiter toutes les données à chaque fois où je souhaite obtenir des informations. Une fois que je connais l'emplacement de chaque patte, il n'y a aucune raison pour que je le calcule à nouveau. De plus, je veux comparer toutes les pattes d'un même chien pour déterminer quel contact appartient à quelle patte (avant/arrière, gauche/droite). Cela deviendrait un gâchis si je continuais à utiliser uniquement des fonctions.

Alors maintenant, je cherche des conseils sur la façon de créer des classes qui me permettront de traiter mes données ( lien vers les données compressées d’un chien ) de manière raisonnable.

141
Ivo Flipse

Comment concevoir une classe.

  1. Écrit les mots. Vous avez commencé à faire ça. Certaines personnes ne le font pas et se demandent pourquoi elles ont des problèmes.

  2. Développez votre ensemble de mots en déclarations simples sur ce que ces objets vont faire. C'est-à-dire, écrivez les différents calculs que vous allez faire sur ces choses. Votre courte liste de 30 chiens, 24 mesures, 4 contacts et plusieurs "paramètres" par contact est intéressante, mais seulement une partie de l'histoire. Vos "emplacements de chaque patte" et "comparez toutes les pattes d'un même chien pour déterminer quel contact appartient à quelle patte" constituent l'étape suivante de la conception d'objet.

  3. Soulignez les noms. Sérieusement. Certaines personnes en débattent, mais je trouve que pour la première fois les développeurs OO), cela aide. Souligne les noms.

  4. Passez en revue les noms. Les noms génériques tels que "paramètre" et "mesure" doivent être remplacés par des noms spécifiques et concrets qui s'appliquent à votre problème dans votre domaine de problèmes. Les détails aident à clarifier le problème. Les génériques élisent simplement les détails.

  5. Pour chaque nom ("contact", "Patte", "chien", etc.), notez les attributs de ce nom et les actions dans lesquelles cet objet est engagé. Ne raccourcis pas ça. Chaque attribut. "Le jeu de données contient 30 chiens", par exemple, est important.

  6. Pour chaque attribut, identifiez s'il s'agit d'une relation avec un nom défini ou avec un autre type de données "primitives" ou "atomiques", telles qu'une chaîne, un flottant ou quelque chose d'irréductible.

  7. Pour chaque action ou opération, vous devez identifier quel nom porte la responsabilité et quels noms ne font que participer. C'est une question de "mutabilité". Certains objets sont mis à jour, d'autres pas. Les objets mutables doivent assumer la responsabilité totale de leurs mutations.

  8. À ce stade, vous pouvez commencer à transformer les noms en définitions de classe. Certains noms collectifs sont des listes, des dictionnaires, des n-uplets, des ensembles ou des nommés, et vous n'avez pas besoin de beaucoup travailler. D'autres classes sont plus complexes, soit en raison de données dérivées complexes, soit en raison de certaines mises à jour/mutations effectuées.

N'oubliez pas de tester chaque classe séparément en utilisant unittest.

En outre, aucune loi ne dit que les classes doivent être mutables. Dans votre cas, par exemple, vous n’avez presque aucune donnée modifiable. Vous disposez de données dérivées, créées par les fonctions de transformation du jeu de données source.

430
S.Lott

Les conseils suivants (similaires au conseil de @S.Lott) sont tirés du livre Début Python: du novice au professionnel

  1. Ecrivez une description de votre problème (que doit faire le problème?). Soulignez tous les noms, verbes et adjectifs.

  2. Parcourez les noms à la recherche de classes potentielles.

  3. Parcourez les verbes à la recherche de méthodes potentielles.

  4. Passez en revue les adjectifs, à la recherche d'attributs potentiels

  5. Allouer des méthodes et des attributs à vos classes

Pour affiner la classe, le livre indique également que nous pouvons procéder comme suit:

  1. Ecrivez (ou imaginez) un ensemble de cas d'utilisation - de scénarios sur la manière dont votre programme peut être utilisé. Essayez de couvrir toutes les fonctionnalités.

  2. Analysez chaque cas d'utilisation étape par étape, en vous assurant que tout ce dont nous avons besoin est couvert.

22
mitchelllc

J'aime l'approche TDD ... Commencez donc par écrire des tests pour déterminer le comportement souhaité. Et écrivez le code qui passe. À ce stade, ne vous inquiétez pas trop de la conception, procurez-vous simplement une suite de tests et un logiciel performant. Ne vous inquiétez pas si vous vous retrouvez avec une seule grande classe laide, avec des méthodes complexes.

Parfois, au cours de ce processus initial, vous trouverez un comportement difficile à tester et qui doit être décomposé, uniquement pour des raisons de testabilité. Cela peut être un indice qu'une classe distincte est justifiée.

Ensuite, la partie amusante ... refactoring. Une fois que vous avez un logiciel de travail, vous pouvez voir les pièces complexes. Souvent, de petites zones de comportement apparaîtront, suggérant une nouvelle classe, mais si non, il suffit de chercher des moyens de simplifier le code. Extraire les objets de service et les objets de valeur. Simplifiez vos méthodes.

Si vous utilisez correctement git (vous utilisez git, n'est-ce pas?), Vous pouvez très rapidement expérimenter une décomposition particulière pendant le refactoring, puis l'abandonner et revenir en arrière si cela ne simplifie pas les choses.

En écrivant d'abord du code de travail testé, vous devriez avoir une idée intime du domaine problématique que vous ne pouviez pas obtenir facilement avec l'approche conception-avant. Écrire des tests et du code Poussez la paralysie "par où dois-je commencer".

13
Les Nightingill

L’idée même de OO design est de faire correspondre le code à votre problème). Ainsi, lorsque, par exemple, vous voulez le premier pas d’un chien, vous faites quelque chose comme:

dog.footstep(0)

Maintenant, il se peut que, dans votre cas, vous ayez besoin de lire votre fichier de données brutes et de calculer les emplacements des pas. Tout cela pourrait être caché dans la fonction footstep () afin que cela ne se produise qu'une seule fois. Quelque chose comme:

 class Dog:
   def __init__(self):
     self._footsteps=None 
   def footstep(self,n):
     if not self._footsteps:
        self.readInFootsteps(...)
     return self._footsteps[n]

[Ceci est maintenant une sorte de modèle de mise en cache. La première fois qu'il lit et lit les données de pas, les fois suivantes, il l'obtient simplement de self._footsteps.]

Mais oui, obtenir OO une conception correcte peut être délicat. Réfléchissez davantage à ce que vous voulez faire avec vos données et indiquez les méthodes que vous devrez appliquer à quelles classes.

3
Spacedman

Écrire vos noms, verbes et adjectifs est une bonne approche, mais je préfère penser à la conception de classe comme à la question quelles données doivent être cachées?

Imaginons que vous ayez un objet Query et un objet Database:

L'objet Query vous aidera à créer et à stocker un magasin de requêtes. Peut-être que vous pourriez rester: Query().select('Country').from_table('User').where('Country == "Brazil"'). Peu importe la syntaxe, c'est votre travail! - la clé est que l'objet vous aide cacher quelque chose, dans ce cas, les données nécessaires pour stocker et générer une requête. La puissance de l'objet provient de la syntaxe d'utilisation (dans ce cas, un chaînage intelligent) et de ne pas avoir besoin de savoir ce qu'il stocke pour le faire fonctionner. Si cela est fait correctement, l'objet Query pourrait générer des requêtes pour plusieurs bases de données. Il stockerait en interne un format spécifique mais pourrait facilement convertir en d’autres formats lors de la sortie (Postgres, MySQL, MongoDB).

Maintenant, réfléchissons à l’objet Database. Qu'est-ce que cela cache et stocke? Bien évidemment, il ne peut pas stocker le contenu complet de la base de données, car c'est pourquoi nous avons une base de données! Alors, quel est le point? L'objectif est de masquer le fonctionnement de la base de données des personnes qui utilisent l'objet Database. De bonnes classes simplifieront le raisonnement lors de la manipulation de l'état interne. Pour cet objet Database, vous pouvez masquer le fonctionnement des appels réseau, des requêtes ou mises à jour par lots, ou fournir une couche de mise en cache.

Le problème est que cet objet Database est énorme. Cela représente comment accéder à une base de données, ainsi, sous les couvertures, elle peut tout faire. Clairement, la mise en réseau, la mise en cache et le traitement par lots sont assez difficiles à gérer en fonction de votre système, il serait donc très utile de les cacher. Mais, comme beaucoup de gens le remarqueront, une base de données est incroyablement complexe et plus on s'éloigne des appels de base de données bruts, plus il est difficile d'optimiser les performances et de comprendre le fonctionnement des choses.

C'est le compromis fondamental de la programmation orientée objet. Si vous choisissez la bonne abstraction, le codage est plus simple (String, Array, Dictionary), si vous choisissez une abstraction trop grande (Base de données, EmailManager, NetworkingManager), elle risque de devenir trop complexe pour vraiment comprendre son fonctionnement ou quoi faire. attendre. Le but est de masquer la complexité, mais une certaine complexité est nécessaire. Une bonne règle est de commencer par éviter les objets Manager et de créer des classes similaires à structs - tout en conservant les données, avec quelques méthodes d'assistance pour les créer/manipuler. pour vous faciliter la vie. Par exemple, dans le cas de EmailManager, commencez par une fonction appelée sendEmail qui prend un objet Email. C'est un point de départ simple et le code est très facile à comprendre.

En ce qui concerne votre exemple, réfléchissez aux données qui doivent être réunies pour calculer ce que vous recherchez. Si vous vouliez savoir à quelle distance un animal marchait, par exemple, vous pourriez avoir les classes AnimalStep et AnimalTrip (collection of AnimalSteps). Maintenant que chaque voyage a toutes les données de pas, il devrait alors être en mesure de résoudre le problème, peut-être que AnimalTrip.calculateDistance() a un sens.

2
Evan Moran

Après avoir écrémé votre code lié, il me semble que vous valez mieux que de concevoir une classe Dog à ce stade. Au lieu de cela, vous devriez utiliser Pandas et images . Une base de données est une table avec des colonnes. Votre cadre de données aurait des colonnes telles que: dog_id, contact_part, contact_time, contact_location, Etc. Pandas utilise Numpy tableaux dans les coulisses, et il a beaucoup de méthodes pratiques pour vous:

  • Sélectionnez un chien, par exemple : my_measurements['dog_id']=='Charly'
  • enregistrer les données: my_measurements.save('filename.pickle')
  • Pensez à utiliser pandas.read_csv() au lieu de lire manuellement les fichiers texte.
2
cyborg