Zen of Python mówi, że powinien istnieć tylko jeden sposób robienia rzeczy - jednak często napotykam problem z podjęciem decyzji, kiedy użyć funkcji, a kiedy metody.
Weźmy trywialny przykład - obiekt ChessBoard. Powiedzmy, że potrzebujemy sposobu, aby uzyskać wszystkie legalne ruchy króla dostępne na planszy. Czy piszemy ChessBoard.get_king_moves () czy get_king_moves (chess_board)?
Oto kilka powiązanych pytań, na które spojrzałem:
- Dlaczego Python używa „magicznych metod”?
- Czy istnieje powód, dla którego łańcuchy Pythona nie mają metody określającej długość łańcucha?
Odpowiedzi, które otrzymałem, były w dużej mierze niejednoznaczne:
Dlaczego Python używa metod dla niektórych funkcji (np. List.index ()), a funkcje dla innych (np. Len (lista))?
Głównym powodem jest historia. Funkcje były używane dla tych operacji, które były ogólne dla grupy typów i które miały działać nawet dla obiektów, które w ogóle nie miały metod (np. Krotki). Wygodne jest również posiadanie funkcji, którą można łatwo zastosować do amorficznej kolekcji obiektów, gdy używasz funkcji funkcjonalnych Pythona (map (), apply () et al).
W rzeczywistości implementacja len (), max (), min () jako funkcji wbudowanej to w rzeczywistości mniej kodu niż implementacja ich jako metod dla każdego typu. Można spierać się o pojedyncze przypadki, ale jest to część Pythona i jest już za późno, aby wprowadzić tak fundamentalne zmiany. Funkcje muszą pozostać, aby uniknąć masowego uszkodzenia kodu.
Chociaż jest to interesujące, powyższe nie mówi zbyt wiele o tym, jaką strategię przyjąć.
Jest to jeden z powodów - w przypadku metod niestandardowych programiści mogliby wybrać inną nazwę metody, na przykład getLength (), length (), getlength () lub jakąkolwiek inną. Python wymusza ścisłe nazewnictwo, aby można było użyć wspólnej funkcji len ().
Nieco bardziej interesujące. Uważam, że funkcje są w pewnym sensie Pythonową wersją interfejsów.
Na koniec od samego Guido :
Rozmowa o zdolnościach / interfejsach sprawiła, że pomyślałem o niektórych z naszych „nieuczciwych” nazw metod specjalnych. W dokumencie Language Reference jest napisane: „Klasa może implementować pewne operacje, które są wywoływane za pomocą specjalnej składni (takie jak operacje arytmetyczne lub indeksowanie i wycinanie), definiując metody o specjalnych nazwach”. Ale są wszystkie te metody ze specjalnymi nazwami, takimi jak
__len__
lub,__unicode__
które wydają się być dostarczane z korzyścią dla funkcji wbudowanych, a nie dla obsługi składni. Przypuszczalnie w Pythonie interfejsu opartego na te metody zamieni się regularnie wymienionych metod działania w ABC, tak że__len__
stałby sięclass container: ... def len(self): raise NotImplemented
Chociaż myśląc o tym więcej, nie rozumiem, dlaczego wszystkie operacje składniowe nie wywoływałyby po prostu odpowiedniej metody o normalnej nazwie na określonym ABC. „
<
”, Na przykład, przypuszczalnie wywołać „object.lessthan
” (albo "comparable.lessthan
„). Więc kolejną korzyścią byłaby możliwość odzwyczajenia Pythona od tej dziwacznej nazwy, co wydaje mi się poprawą HCI .Hm. Nie jestem pewien, czy się zgadzam (wyobraź sobie :-).
Istnieją dwa fragmenty „uzasadnienia Pythona”, które chciałbym najpierw wyjaśnić.
Przede wszystkim wybrałem len (x) zamiast x.len () z powodów związanych z HCI (
def __len__()
pojawiło się znacznie później). W rzeczywistości istnieją dwa powiązane ze sobą powody, oba HCI:(a) W przypadku niektórych operacji notacja przedrostków po prostu czyta lepiej niż operacje na postfiksach - prefiksach (i wrostkach!) mają długą tradycję w matematyce, która lubi notacje, gdzie wizualizacje pomagają matematykowi w myśleniu o problemie. Porównaj łatwość, z jaką przepisujemy formułę
x*(a+b)
na,x*a + x*b
z niezdarnością robienia tego samego przy użyciu surowej notacji OO.(b) Kiedy czytam kod, który mówi,
len(x)
że wiem , że prosi o długość czegoś. To mówi mi o dwóch rzeczach: wynik jest liczbą całkowitą, a argumentem jest jakiś kontener. Wręcz przeciwnie, kiedy czytamx.len()
, muszę już wiedzieć, żex
jest to jakiś kontener implementujący interfejs lub dziedziczący po klasie, która ma standardlen()
. Świadek zamieszanie niekiedy mają kiedy klasa, która nie realizuje mapowanie maget()
lubkeys()
metody, lub coś, co nie jest plikiem mawrite()
metody.Mówiąc to samo w inny sposób, widzę „len” jako operację wbudowaną . Nie chciałbym tego stracić. Nie jestem pewien, czy miałeś to na myśli, czy nie, ale „def len (self):…” z pewnością brzmi tak, jakbyś chciał zdegradować to do zwykłej metody. Jestem zdecydowanie -1 w tej sprawie.
Drugie uzasadnienie Pythona, które obiecałem wyjaśnić, to powód, dla którego wybrałem specjalne metody wyszukiwania,
__special__
a nie tylkospecial
. Spodziewałem się wielu operacji, które klasy mogłyby chcieć przesłonić, niektóre standardowe (np.__add__
Lub__getitem__
), niektóre nie tak standardowe (np. Pikle__reduce__
przez długi czas nie były w ogóle obsługiwane w kodzie C). Nie chciałem, aby te specjalne operacje używały zwykłych nazw metod, ponieważ wcześniej istniejące klasy lub klasy napisane przez użytkowników bez encyklopedycznej pamięci dla wszystkich metod specjalnych byłyby podatne na przypadkowe zdefiniowanie operacji, których nie zamierzali zaimplementować , co może mieć katastrofalne skutki. Ivan Krstić wyjaśnił to bardziej zwięźle w swoim przesłaniu, które dotarło po tym, jak to wszystko napisałem.- --Guido van Rossum (strona domowa: http://www.python.org/~guido/ )
Rozumiem to, że w niektórych przypadkach notacja przedrostków ma po prostu więcej sensu (tj. Duck.quack ma więcej sensu niż szarlatan (Duck) z językowego punktu widzenia) i znowu funkcje pozwalają na „interfejsy”.
W takim przypadku przypuszczam, że zaimplementowałbym get_king_moves wyłącznie na podstawie pierwszego punktu Guido. Ale to wciąż pozostawia wiele otwartych pytań dotyczących, powiedzmy, implementacji klasy stosu i kolejki z podobnymi metodami push i pop - czy powinny to być funkcje czy metody? (tutaj zgadywałbym funkcje, bo naprawdę chcę zasygnalizować interfejs push-pop)
TLDR: Czy ktoś może wyjaśnić, jaka powinna być strategia podejmowania decyzji, kiedy używać funkcji, a kiedy metod?
źródło
X.frob
czyX.__frob__
wolnostojącefrob
.Odpowiedzi:
Moja ogólna zasada jest taka - czy operacja jest wykonywana na obiekcie, czy przez obiekt?
jeśli jest to wykonywane przez obiekt, powinna to być operacja składowa. Jeśli mogłoby to odnosić się również do innych rzeczy, lub jest wykonywane przez coś innego do obiektu, to powinno być funkcją (lub być może członkiem czegoś innego).
Wprowadzając programowanie, tradycyjnym (aczkolwiek niepoprawnym wdrożeniem) jest opisywanie obiektów w kategoriach obiektów świata rzeczywistego, takich jak samochody. Wspomniałeś o kaczce, więc chodźmy z tym.
W kontekście analogii „obiekty są rzeczywistymi rzeczami” „poprawne jest” dodanie metody klasy do wszystkiego, co obiekt może zrobić. Więc powiedz, że chcę zabić kaczkę, czy dodać .kill () do kaczki? Nie ... o ile wiem, zwierzęta nie popełniają samobójstw. Dlatego jeśli chcę zabić kaczkę, powinienem zrobić to:
Odchodząc od tej analogii, dlaczego używamy metod i klas? Ponieważ chcemy zawierać dane i, miejmy nadzieję, ustrukturyzować nasz kod w taki sposób, aby w przyszłości można go było wielokrotnie używać i rozszerzać. To prowadzi nas do pojęcia hermetyzacji, które jest tak bliskie projektowi OO.
Zasada hermetyzacji jest tak naprawdę tym, do czego to się sprowadza: jako projektant powinieneś ukryć wszystko, co dotyczy implementacji i elementów wewnętrznych klas, do których dostęp nie jest absolutnie konieczny dla jakiegokolwiek użytkownika lub innego programisty. Ponieważ mamy do czynienia z instancjami klas, ogranicza się to do „jakie operacje są kluczowe w tej instancji ”. Jeśli operacja nie jest specyficzna dla instancji, nie powinna być funkcją składową.
TL; DR : co powiedział @Bryan. Jeśli działa na instancji i potrzebuje dostępu do danych, które są wewnętrzne dla instancji klasy, powinna być funkcją składową.
źródło
Skorzystaj z zajęć, jeśli chcesz:
1) Oddziel wywołanie kodu od szczegółów implementacji - korzystając z abstrakcji i hermetyzacji .
2) Gdy chcesz być substytutem innych obiektów - wykorzystując polimorfizm .
3) Gdy chcesz ponownie użyć kodu dla podobnych obiektów - korzystając z dziedziczenia .
Użyj funkcji dla wywołań, które mają sens w wielu różnych typach obiektów - na przykład wbudowane funkcje len i repr mają zastosowanie do wielu rodzajów obiektów.
Biorąc to pod uwagę, wybór czasami sprowadza się do gustu. Pomyśl o tym, co jest najwygodniejsze i najbardziej czytelne w przypadku typowych rozmów. Na przykład, który byłby lepszy
(x.sin()**2 + y.cos()**2).sqrt()
czysqrt(sin(x)**2 + cos(y)**2)
?źródło
Oto prosta zasada: jeśli kod działa na pojedynczą instancję obiektu, użyj metody. Jeszcze lepiej: użyj metody, chyba że istnieje ważny powód, aby zapisać ją jako funkcję.
W swoim konkretnym przykładzie chcesz, aby wyglądało to tak:
Nie myśl nad tym. Zawsze używaj metod, aż dojdzie do momentu, w którym powiesz sobie „nie ma sensu robić tego metodą”, w takim przypadku możesz utworzyć funkcję.
źródło
len()
, można również argumentować, że projekt ma sens, chociaż osobiście uważam, że funkcje też nie byłyby tam takie złe - mielibyśmy po prostu inną konwencję, któralen()
zawsze musi zwracać integer (ale ze wszystkimi dodatkowymi problemami backcomp nie polecałbym tego również dla Pythona)Zwykle myślę o przedmiocie jak o osobie.
Atrybuty to imię i nazwisko osoby, wzrost, rozmiar buta itp.
Metody i funkcje to operacje, które osoba może wykonać.
Jeśli operacja mogłaby być wykonana przez jakąkolwiek starą osobę, bez wymagania czegokolwiek unikalnego dla tej jednej konkretnej osoby (i bez zmiany czegokolwiek na tej jednej konkretnej osobie), to jest to funkcja i powinna być zapisana jako taka.
Jeśli operacja działa na osobę (np. Jedzenie, chodzenie, ...) lub wymaga czegoś wyjątkowego dla tej osoby (np. Taniec, pisanie książki, ...), to powinna to być metoda .
Oczywiście nie zawsze jest to trywialne, aby przetłumaczyć to na konkretny obiekt, z którym pracujesz, ale uważam, że jest to dobry sposób, aby o tym pomyśleć.
źródło
height(person)
, prawdaperson.height
?are_married(person1, person2)
? To zapytanie jest bardzo ogólne i dlatego powinno być funkcją, a nie metodą.Generalnie używam klas do implementacji logicznego zestawu możliwości dla jakiejś rzeczy , tak że w pozostałej części mojego programu mogę o tym myśleć , nie martwiąc się o wszystkie małe problemy, które składają się na jej implementację.
Wszystko, co jest częścią tej podstawowej abstrakcji mówiącej o tym, „co można z czymś zrobić ”, powinno być zwykle metodą. Zwykle obejmuje to wszystko, co może coś zmienić , ponieważ stan danych wewnętrznych jest zwykle uważany za prywatny i nie jest częścią logicznej idei „co można z rzeczą zrobić ”.
Kiedy dochodzisz do operacji wyższego poziomu, zwłaszcza jeśli dotyczą wielu rzeczy , stwierdzam, że są one zwykle wyrażane w najbardziej naturalny sposób jako funkcje, jeśli można je zbudować z publicznej abstrakcji rzeczy bez konieczności specjalnego dostępu do elementów wewnętrznych (chyba że są one) metody innego obiektu). Ma to tę dużą zaletę, że kiedy zdecyduję się całkowicie przepisać wewnętrzne zasady działania mojej rzeczy (bez zmiany interfejsu), mam po prostu mały podstawowy zestaw metod do przepisania, a następnie wszystkie funkcje zewnętrzne napisane pod kątem tych metod będzie po prostu działać. Uważam, że upieranie się, że wszystkie operacje związane z klasą X są metodami w klasie X, prowadzi do zbyt skomplikowanych klas.
Zależy to jednak od kodu, który piszę. W przypadku niektórych programów modeluję je jako zbiór obiektów, których interakcje prowadzą do zachowania programu; tutaj najważniejsza funkcjonalność jest ściśle powiązana z pojedynczym obiektem, a więc jest implementowana w metodach z rozproszeniem funkcji użytkowych. Dla innych programów najważniejszy jest zestaw funkcji, które manipulują danymi, a klasy są używane tylko do implementacji naturalnych „typów kaczych”, którymi te funkcje zajmują się.
źródło
Można powiedzieć, że „w obliczu dwuznaczności odrzućcie pokusę zgadywania”.
Jednak to nawet nie domysł. Masz absolutną pewność, że wyniki obu podejść są takie same, ponieważ rozwiązują Twój problem.
Uważam, że dobrze jest mieć wiele sposobów osiągania celów. Pokornie powiedziałbym, tak jak inni użytkownicy, aby zastosować ten, który „smakuje lepiej” / jest bardziej intuicyjny pod względem języka.
źródło