Come posso progettare una classe in Python?


143

Ho avuto un aiuto davvero eccezionale sulle mie precedenti domande per rilevare zampe e dita dei piedi all'interno di una zampa , ma tutte queste soluzioni funzionano solo per una misurazione alla volta.

Ora ho i dati che consistono:

  • circa 30 cani;
  • ognuno ha 24 misurazioni (divise in diversi sottogruppi);
  • ogni misura ha almeno 4 contatti (uno per ogni zampa) e
    • ogni contatto è diviso in 5 parti e
    • ha diversi parametri, come tempo di contatto, posizione, forza totale ecc.

testo alternativo

Ovviamente incollare tutto in un unico grande oggetto non lo taglierà, quindi ho pensato che avrei dovuto usare le classi invece dell'attuale serie di funzioni. Ma anche se ho letto il capitolo di Learning Python sulle classi, non riesco ad applicarlo al mio codice ( link GitHub )

Mi sembra anche piuttosto strano elaborare tutti i dati ogni volta che voglio ottenere alcune informazioni. Una volta che conosco le posizioni di ogni zampa, non ho motivo di calcolarlo di nuovo. Inoltre, voglio confrontare tutte le zampe dello stesso cane per determinare quale contatto appartiene a quale zampa (anteriore / posteriore, sinistra / destra). Questo diventerebbe un casino se continuo ad usare solo le funzioni.

Quindi ora sto cercando consigli su come creare classi che mi consentano di elaborare i miei dati ( link ai dati compressi di un cane ) in modo ragionevole.


4
Potresti anche prendere in considerazione l'utilizzo di un database (come sqlite: docs.python.org/library/sqlite3.html ). Potresti scrivere un programma che legge i tuoi enormi file di dati e li converte in righe nelle tabelle del database. Quindi come seconda fase è possibile scrivere programmi che estraggono dati dal database per fare ulteriori analisi.
unutbu,

Vuoi dire qualcosa come ho chiesto qui @ubutbu? Sto pensando di farlo, ma prima vorrei essere in grado di elaborare tutti i dati in modo più organizzato
Ivo Flipse,

Risposte:


434

Come progettare una classe.

  1. Scrivi le parole. Hai iniziato a farlo. Alcune persone non si chiedono perché abbiano problemi.

  2. Espandi il tuo insieme di parole in semplici affermazioni su cosa faranno questi oggetti. Vale a dire, annota i vari calcoli che farai su queste cose. Il tuo breve elenco di 30 cani, 24 misurazioni, 4 contatti e diversi "parametri" per contatto è interessante, ma solo una parte della storia. Le tue "posizioni di ogni zampa" e "confronta tutte le zampe dello stesso cane per determinare quale contatto appartiene a quale zampa" sono il passo successivo nella progettazione degli oggetti.

  3. Sottolinea i nomi. Sul serio. Alcune persone discutono del valore di questo, ma trovo che sia utile agli sviluppatori OO per la prima volta. Sottolinea i nomi.

  4. Rivedi i nomi. I nomi generici come "parametro" e "misurazione" devono essere sostituiti con nomi specifici e concreti che si applicano al tuo problema nel tuo dominio problematico. Le specifiche aiutano a chiarire il problema. Generics elide semplicemente i dettagli.

  5. Per ogni sostantivo ("contatto", "zampa", "cane", ecc.) Annota gli attributi di quel nome e le azioni in cui l'oggetto si impegna. Non abbreviare questo. Ogni attributo. "Il set di dati contiene 30 cani", ad esempio, è importante.

  6. Per ogni attributo, identifica se si tratta di una relazione con un nome definito, o qualche altro tipo di dati "primitivi" o "atomici" come una stringa o un galleggiante o qualcosa di irriducibile.

  7. Per ogni azione o operazione, devi identificare quale nome ha la responsabilità e quali nomi si limitano a partecipare. È una questione di "mutabilità". Alcuni oggetti vengono aggiornati, altri no. Gli oggetti mutabili devono possedere la totale responsabilità delle loro mutazioni.

  8. A questo punto, puoi iniziare a trasformare i sostantivi in ​​definizioni di classe. Alcuni nomi collettivi sono elenchi, dizionari, tuple, set o denominazioni di coppia e non è necessario fare molto lavoro. Altre classi sono più complesse, a causa di dati derivati ​​complessi o a causa di alcuni aggiornamenti / mutazione che vengono eseguiti.

Non dimenticare di testare ogni classe in isolamento usando unittest.

Inoltre, non esiste una legge che dice che le classi devono essere mutabili. Nel tuo caso, ad esempio, non hai quasi dati mutabili. Quello che hai sono dati derivati, creati da funzioni di trasformazione dal set di dati di origine.


24

I seguenti consigli (simili ai consigli di @ S.Lott) sono tratti dal libro, Beginning Python: dal principiante al professionista

  1. Scrivi una descrizione del tuo problema (cosa dovrebbe fare il problema?). Sottolinea tutti i nomi, i verbi e gli aggettivi.

  2. Passa attraverso i sostantivi, cercando potenziali classi.

  3. Passa attraverso i verbi, cercando potenziali metodi.

  4. Passa attraverso gli aggettivi, cercando potenziali attributi

  5. Assegna metodi e attributi alle tue classi

Per perfezionare la lezione, il libro suggerisce anche che possiamo fare quanto segue:

  1. Scrivi (o immagina ) una serie di casi d' uso - scenari di come il tuo programma può essere usato. Prova a coprire tutto funzionalmente.

  2. Pensa passo dopo passo a ogni caso d'uso, assicurandoti che tutto ciò di cui abbiamo bisogno sia coperto.


Sarebbe bello avere alcuni esempi del tipo di frasi che dovremmo scrivere.
endolito il

14

Mi piace l'approccio TDD ... Quindi inizia scrivendo test per quello che vuoi che sia il comportamento. E scrivi il codice che passa. A questo punto, non preoccuparti troppo del design, basta ottenere una suite di test e un software che passa. Non preoccuparti se finisci con un'unica grande brutta classe, con metodi complessi.

A volte, durante questo processo iniziale, troverai un comportamento difficile da testare e che deve essere decomposto, solo per testabilità. Questo può essere un suggerimento che è garantita una classe separata.

Quindi la parte divertente ... refactoring. Dopo aver software funzionante puoi vedere i pezzi complessi. Spesso appariranno piccole sacche di comportamento, che suggeriscono una nuova classe, ma in caso contrario, cercano solo modi per semplificare il codice. Estrarre oggetti di servizio e oggetti valore. Semplifica i tuoi metodi.

Se stai usando git correttamente (stai usando git, vero?), Puoi sperimentare molto rapidamente una particolare decomposizione durante il refactoring, quindi abbandonarlo e tornare indietro se non semplifica le cose.

Scrivendo prima il codice di lavoro testato dovresti ottenere una visione intima del dominio del problema che non potresti facilmente ottenere con l'approccio di progettazione iniziale. Scrivere test e codice ti spinge oltre la paralisi "da dove comincio".


1
Anch'io sono d'accordo con questa risposta, sebbene la suddivisione del problema e l'identificazione di possibili classi (ovvero fare un'architettura software "appena sufficiente") può essere molto utile se il problema verrà affrontato in parallelo da diversi membri del team.
Ben Smith,

3

L'idea del design di OO è quella di rendere il tuo codice mappato al tuo problema, quindi quando, per esempio, vuoi il primo passo di un cane, fai qualcosa del tipo:

dog.footstep(0)

Ora, potrebbe essere che nel tuo caso sia necessario leggere il file di dati non elaborati e calcolare le posizioni dei passi. Tutto ciò potrebbe essere nascosto nella funzione footstep () in modo che accada solo una volta. Qualcosa di simile a:

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

[Questo è ora una sorta di modello di memorizzazione nella cache. La prima volta che va e legge i dati del passo, le volte successive li ottiene da self._footsteps.]

Ma sì, ottenere il giusto design OO può essere complicato. Pensa di più alle cose che vuoi fare ai tuoi dati e questo ti informerà su quali metodi dovrai applicare a quali classi.


2

Scrivere nomi, verbi, aggettivi è un ottimo approccio, ma preferisco pensare al design di classe come a porre la domanda quali dati dovrebbero essere nascosti ?

Immagina di avere un Queryoggetto e un Databaseoggetto:

L' Queryoggetto ti aiuterà a creare e memorizzare una query - store, è la chiave qui, in quanto una funzione potrebbe aiutarti a crearne uno altrettanto facilmente. Forse si poteva stare: Query().select('Country').from_table('User').where('Country == "Brazil"'). Non importa esattamente la sintassi: quello è il tuo lavoro! - la chiave è che l'oggetto ti aiuta a nascondere qualcosa , in questo caso i dati necessari per archiviare e generare una query. Il potere dell'oggetto deriva dalla sintassi del suo utilizzo (in questo caso un concatenamento intelligente) e non è necessario sapere cosa memorizza per farlo funzionare. Se eseguito correttamente, l' Queryoggetto potrebbe generare query per più di un database. Memorizzerebbe internamente un formato specifico ma potrebbe facilmente convertirsi in altri formati durante l'output (Postgres, MySQL, MongoDB).

Ora pensiamo attraverso l' Databaseoggetto. Cosa nasconde e memorizza? Beh, chiaramente non può memorizzare l'intero contenuto del database, poiché è per questo che abbiamo un database! Quindi qual è il punto? L'obiettivo è nascondere il funzionamento del database alle persone che utilizzano l' Databaseoggetto. Le buone classi semplificheranno il ragionamento durante la manipolazione dello stato interno. Per questo Databaseoggetto è possibile nascondere il funzionamento delle chiamate in rete o eseguire query o aggiornamenti in batch o fornire un livello di memorizzazione nella cache.

Il problema è che questo Databaseoggetto è ENORME. Rappresenta come accedere a un database, quindi sotto le coperte potrebbe fare qualsiasi cosa. Chiaramente la rete, la memorizzazione nella cache e il batch sono abbastanza difficili da gestire a seconda del sistema, quindi nasconderli sarebbe molto utile. Ma, come noteranno molte persone, un database è follemente complesso e più lontano dalle chiamate di DB non elaborate si ottiene, più è difficile ottimizzare le prestazioni e capire come funzionano le cose.

Questo è il compromesso fondamentale di OOP. Se scegli l'astrazione giusta, la codifica diventa più semplice (String, Array, Dictionary), se scegli un'astrazione troppo grande (Database, EmailManager, NetworkingManager), potrebbe diventare troppo complesso per capire davvero come funziona o cosa aspettarsi. L'obiettivo è nascondere la complessità , ma è necessaria una certa complessità. Una buona regola empirica è iniziare evitando Manageroggetti e creare invece classi simili structs: tutto ciò che fanno è conservare i dati, con alcuni metodi di supporto per creare / manipolare i dati per semplificarti la vita. Ad esempio, nel caso di EmailManagerinizio con una funzione chiamata sendEmailche accetta un Emailoggetto. Questo è un semplice punto di partenza e il codice è molto facile da capire.

Per quanto riguarda il tuo esempio, pensa a quali dati devono essere uniti per calcolare ciò che stai cercando. Se volessi sapere fino a che punto camminava un animale, ad esempio, potresti avere AnimalStepe AnimalTrip(collezione di AnimalSteps) classi. Ora che ogni viaggio ha tutti i dati Step, allora dovrebbe essere in grado di capire cose al riguardo, forse AnimalTrip.calculateDistance()ha senso.


2

Dopo aver scremato il tuo codice collegato, mi sembra che a questo punto è meglio non progettare una classe Dog. Piuttosto, dovresti usare Panda e frame di dati . Un dataframe è una tabella con colonne. Si dataframe avrebbe colonne quali: dog_id, contact_part, contact_time, contact_location, ecc Panda utilizza matrici NumPy dietro le quinte, e ha molti metodi di convenienza per voi:

  • Seleziona un cane per esempio: my_measurements['dog_id']=='Charly'
  • salva i dati: my_measurements.save('filename.pickle')
  • Prendi in considerazione l'utilizzo pandas.read_csv()anziché la lettura manuale dei file di testo.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.