Jak zaprojektować klasę w Pythonie?

143

Otrzymałem naprawdę niesamowitą pomoc w moich poprzednich pytaniach dotyczących wykrywania łap i palców u nóg w łapie , ale wszystkie te rozwiązania działają tylko dla jednego pomiaru naraz.

Teraz mam dane składające się z:

  • około 30 psów;
  • każdy ma 24 pomiary (podzielone na kilka podgrup);
  • każdy pomiar ma co najmniej 4 styki (po jednym na każdą łapę) i
    • każdy kontakt jest podzielony na 5 części i
    • ma kilka parametrów, takich jak czas kontaktu, lokalizacja, całkowita siła itp.

tekst alternatywny

Oczywiście umieszczenie wszystkiego w jednym dużym obiekcie nie da rady, więc doszedłem do wniosku, że muszę użyć klas zamiast obecnych mnóstwa funkcji. Ale mimo że przeczytałem rozdział Learning Python o klasach, nie udało mi się zastosować go do własnego kodu ( link GitHub )

Wydaje mi się również, że przetwarzanie wszystkich danych za każdym razem, gdy chcę uzyskać jakieś informacje, jest dość dziwne . Kiedy już poznam lokalizację każdej łapy, nie ma powodu, bym musiał to ponownie obliczać. Ponadto chcę porównać wszystkie łapy tego samego psa, aby określić, który kontakt należy do której łapy (przednia / tylna, lewa / prawa). Byłoby to bałaganem, gdybym nadal używał tylko funkcji.

Więc teraz szukam porady, jak stworzyć zajęcia, które pozwolą mi przetwarzać moje dane ( link do spakowanych danych jednego psa ) w rozsądny sposób.

Ivo Flipse
źródło
4
Możesz również rozważyć użycie bazy danych (takiej jak sqlite: docs.python.org/library/sqlite3.html ). Możesz napisać program, który czyta twoje ogromne pliki danych i konwertuje je na wiersze w tabelach bazy danych. Następnie jako drugi etap możesz napisać programy, które będą pobierać dane z bazy danych w celu dalszej analizy.
unutbu

Odpowiedzi:

434

Jak zaprojektować zajęcia.

  1. Zapisz słowa. Zacząłeś to robić. Niektórzy ludzie tego nie robią i zastanawiają się, dlaczego mają problemy.

  2. Rozszerz swój zestaw słów na proste stwierdzenia dotyczące tego, co te obiekty będą robić. To znaczy, zapisz różne obliczenia, które będziesz wykonywać na tych rzeczach. Twoja krótka lista 30 psów, 24 pomiary, 4 kontakty i kilka "parametrów" na kontakt jest interesująca, ale to tylko część historii. Twoje „lokalizacje każdej łapy” i „porównaj wszystkie łapy tego samego psa, aby określić, który kontakt należy do której łapy” to kolejny krok w projektowaniu obiektów.

  3. Podkreśl rzeczowniki. Poważnie. Niektórzy debatują nad wartością tego, ale uważam, że dla programistów OO po raz pierwszy to pomaga. Podkreśl rzeczowniki.

  4. Przejrzyj rzeczowniki. Rzeczowniki rodzajowe, takie jak „parametr” i „pomiar”, należy zastąpić konkretnymi, konkretnymi rzeczownikami, które dotyczą problemu w domenie, w której występuje problem. Szczegółowe informacje pomagają w wyjaśnieniu problemu. Leki generyczne po prostu usuwają szczegóły.

  5. Dla każdego rzeczownika („kontakt”, „łapa”, „pies” itp.) Zapisz atrybuty tego rzeczownika oraz czynności, w które ten przedmiot się angażuje. Nie skracaj tego. Każdy atrybut. Na przykład „zbiór danych zawiera 30 psów” jest ważny.

  6. Dla każdego atrybutu określ, czy jest to związek z określonym rzeczownikiem, czy jakimś innym rodzajem danych „pierwotnych” lub „atomowych”, takich jak ciąg znaków, zmiennoprzecinkowy lub coś nieredukowalnego.

  7. W przypadku każdej czynności lub operacji musisz określić, który rzeczownik jest odpowiedzialny, a które tylko uczestniczą. To kwestia „zmienności”. Niektóre obiekty są aktualizowane, inne nie. Zmienne obiekty muszą ponosić całkowitą odpowiedzialność za swoje mutacje.

  8. W tym momencie możesz zacząć przekształcać rzeczowniki w definicje klas. Niektóre rzeczowniki zbiorowe to listy, słowniki, krotki, zbiory lub krotki z imionami i nie musisz wykonywać zbyt wiele pracy. Inne klasy są bardziej złożone, albo ze względu na złożone dane pochodne, albo z powodu przeprowadzanej aktualizacji / mutacji.

Nie zapomnij przetestować każdej klasy oddzielnie, używając unittest.

Nie ma też prawa, które mówi, że klasy muszą być zmienne. Na przykład w twoim przypadku nie masz prawie żadnych danych, które można modyfikować. To, co masz, to dane pochodne, utworzone przez funkcje transformacji ze źródłowego zestawu danych.

S.Lott
źródło
24

Następujące porady (podobne do rady @ S.Lott) pochodzą z książki Beginning Python: From Novice to Professional

  1. Zapisz opis swojego problemu (co powinien zrobić?). Podkreśl wszystkie rzeczowniki, czasowniki i przymiotniki.

  2. Przejrzyj rzeczowniki, szukając potencjalnych klas.

  3. Przejrzyj czasowniki, szukając potencjalnych metod.

  4. Przejrzyj przymiotniki, szukając potencjalnych atrybutów

  5. Przydziel metody i atrybuty do swoich klas

Książka radzi również, aby udoskonalić klasę, wykonując następujące czynności:

  1. Zapisz (lub wymyśl ) zestaw przypadków użycia - scenariusze pokazujące, jak Twój program może być używany. Spróbuj objąć wszystkie funkcjonalne.

  2. Przemyśl każdy przypadek użycia krok po kroku, upewniając się, że wszystko, czego potrzebujemy, zostało uwzględnione.

mitchelllc
źródło
Dobrze byłoby mieć kilka przykładów zdań, które mamy pisać.
endolith
14

Podoba mi się podejście TDD ... Zacznij więc od napisania testów na to, jakie zachowanie ma wyglądać. I pisz kod, który przechodzi. W tym momencie nie przejmuj się zbytnio projektowaniem, po prostu zdobądź pakiet testów i oprogramowanie, które przejdzie pomyślnie. Nie martw się, jeśli skończysz z jedną wielką brzydką klasą ze złożonymi metodami.

Czasami podczas tego początkowego procesu można znaleźć zachowanie, które jest trudne do przetestowania i musi zostać rozłożone, tylko w celu przetestowania. Może to być wskazówką, że oddzielna klasa jest uzasadniona.

Potem fajna część ... refaktoryzacja. Po uruchomieniu oprogramowania możesz zobaczyć złożone elementy. Często pojawiają się małe obszary zachowań, sugerujące nową klasę, ale jeśli nie, po prostu poszukaj sposobów na uproszczenie kodu. Wyodrębnij obiekty usług i obiekty wartości. Uprość swoje metody.

Jeśli używasz git poprawnie (używasz git, prawda?), Możesz bardzo szybko eksperymentować z jakimś konkretnym rozkładem podczas refaktoryzacji, a następnie porzucić go i powrócić, jeśli to nie upraszcza rzeczy.

Pisząc najpierw przetestowany działający kod, powinieneś uzyskać dokładny wgląd w dziedzinę problemu, którego nie można łatwo uzyskać dzięki podejściu polegającemu na projektowaniu. Pisanie testów i kodu wypycha Cię poza paraliż „od czego mam zacząć”.

Les Nightingill
źródło
1
Ja również zgadzam się z tą odpowiedzią, chociaż rozbicie problemu i zidentyfikowanie możliwych klas (tj. Zrobienie „wystarczającej” architektury oprogramowania) może być bardzo przydatne, jeśli problem będzie pracował równolegle przez kilku członków zespołu.
Ben Smith
3

Cała idea projektowania OO polega na zrobieniu mapy kodu na Twój problem, więc gdy na przykład chcesz postawić pierwszy krok psa, robisz coś takiego:

dog.footstep(0)

Teraz może się zdarzyć, że w twoim przypadku będziesz musiał wczytać swój surowy plik danych i obliczyć lokalizacje kroków. Wszystko to można by ukryć w funkcji footstep (), aby zdarzyło się to tylko raz. Coś jak:

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

[To jest teraz rodzaj wzorca buforowania. Za pierwszym razem, gdy idzie i odczytuje dane dotyczące kroków, za każdym razem po prostu pobiera je z siebie._footsteps.]

Ale tak, prawidłowe zaprojektowanie OO może być trudne. Pomyśl więcej o tym, co chcesz zrobić ze swoimi danymi, a to poinformuje Cię, jakie metody musisz zastosować do jakich klas.

Spacedman
źródło
2

Pisanie rzeczowników, czasowników, przymiotników to świetne podejście, ale wolę myśleć o projektowaniu klas jako o zadawaniu pytania, jakie dane powinny być ukryte ?

Wyobraź sobie, że masz Queryprzedmiot i Databaseprzedmiot:

QueryObiekt pomoże Ci tworzyć i przechowywać zapytania - sklep, jest kluczem tutaj, jako funkcja może pomóc utworzyć tak łatwo. Może uda się zatrzymać: Query().select('Country').from_table('User').where('Country == "Brazil"'). Nie ma znaczenia dokładnie składnia - to twoja praca! - kluczem jest obiekt, który pomaga ci coś ukryć , w tym przypadku dane niezbędne do przechowywania i wysyłania zapytania. Siła obiektu pochodzi ze składni używania go (w tym przypadku sprytnego tworzenia łańcucha) i nie musi wiedzieć, co przechowuje, aby działał. Jeśli zostanie to zrobione poprawnie, Queryobiekt może wysyłać zapytania do więcej niż jednej bazy danych. Wewnętrznie przechowuje określony format, ale można go łatwo konwertować na inne formaty podczas drukowania (Postgres, MySQL, MongoDB).

Zastanówmy się teraz nad Databaseobiektem. Co to ukrywa i przechowuje? Oczywiście nie może przechowywać pełnej zawartości bazy danych, ponieważ właśnie dlatego mamy bazę danych! Więc o co chodzi? Celem jest ukrycie sposobu działania bazy danych przed osobami korzystającymi z Databaseobiektu. Dobre klasy upraszczają rozumowanie podczas manipulowania stanem wewnętrznym. W przypadku tego Databaseobiektu można ukryć sposób działania wywołań sieciowych lub wsadowych zapytań lub aktualizacji albo zapewnić warstwę buforowania.

Problem w tym, że ten Databaseobiekt jest OGROMNY. Pokazuje, jak uzyskać dostęp do bazy danych, więc pod osłonami może zrobić wszystko i wszystko. Oczywiście praca w sieci, buforowanie i przetwarzanie wsadowe są dość trudne w zależności od systemu, więc ukrycie ich byłoby bardzo pomocne. Ale, jak zauważy wiele osób, baza danych jest niesamowicie złożona, a im dalej od surowych wywołań bazy danych otrzymujesz, tym trudniej jest dostroić się do wydajności i zrozumieć, jak to działa.

To jest podstawowy kompromis z OOP. Jeśli wybierzesz odpowiednią abstrakcję, ułatwi to kodowanie (ciąg znaków, tablica, słownik), jeśli wybierzesz abstrakcję, która jest zbyt duża (baza danych, EmailManager, NetworkingManager), może to stać się zbyt skomplikowane, aby naprawdę zrozumieć, jak to działa lub co robić oczekiwać. Celem jest ukrycie złożoności , ale pewna złożoność jest konieczna. Dobrą praktyczną zasadą jest unikanie Managerobiektów, a zamiast tego tworzenie klas, które są podobne structs- wszystko, co robią, to przechowywanie danych, z pewnymi pomocniczymi metodami tworzenia / manipulowania danymi, aby ułatwić Ci życie. Na przykład w przypadku EmailManageruruchomienia funkcji wywoływanej, sendEmailktóra przyjmuje Emailobiekt. To jest prosty punkt wyjścia, a kod jest bardzo łatwy do zrozumienia.

Na przykład zastanów się, jakie dane muszą być razem, aby obliczyć to, czego szukasz. Jeśli chcesz na przykład wiedzieć, jak daleko chodzi zwierzę, możesz mieć zajęcia AnimalStepi AnimalTrip(zbiór AnimalSteps). Teraz, gdy każda podróż ma wszystkie dane dotyczące kroków, powinna być w stanie dowiedzieć się o tym, być może AnimalTrip.calculateDistance()ma sens.

Evan Moran
źródło
2

Po przejrzeniu twojego powiązanego kodu wydaje mi się, że na tym etapie lepiej nie projektować klasy Dog. Przeciwnie, należy użyć Pandy i dataframes . Dataframe to tabela z kolumnami. Byś dataframe mają kolumny takie jak: dog_id, contact_part, contact_time, contact_location, itd Pandy wykorzystuje tablice numpy za kulisami, i ma wiele metod wygoda dla Ciebie:

  • Wybierz psa np .: my_measurements['dog_id']=='Charly'
  • Zapisz dane: my_measurements.save('filename.pickle')
  • Rozważ użycie pandas.read_csv()zamiast ręcznego czytania plików tekstowych.
cyborg
źródło