Kiedy w Pythonie powinienem używać funkcji zamiast metody?

Zen Pythona mówi, że powinien być tylko jeden sposób na zrobienie rzeczy - jednak często spotykam się z problemem decydowania, kiedy użyć funkcji, a kiedy użyć metody.

Weźmy trywialny przykład-obiekt szachownicy. Powiedzmy, że potrzebujemy jakiegoś sposobu, aby uzyskać wszystkie legalne ruchy Króla dostępne na tablicy. Czy piszemy szachownicę?get_king_moves () czy get_king_moves (chess_board)?

Oto kilka powiązanych pytań, na które spojrzałem:

Odpowiedzi jakie otrzymałem były w dużej mierze niejednoznaczne:

Dlaczego Python używa metod dla niektórych funkcji (np. list.index()) ale funkcje dla innych(np. len (list))?

Głównym powodem jest historia. Do tych operacji zostały użyte funkcje, które były ogólne dla grupy typów i które były przeznaczone do pracy nawet dla obiekty, które w ogóle nie miały metod (np. krotki). Wygodne jest również posiadanie funkcji, która może łatwo można zastosować do amorficznej kolekcji obiektów, gdy używasz funkcje funkcjonalne Pythona (map (), apply () i in.).

W rzeczywistości implementacja len (), max (), min () jako wbudowanej funkcji jest w rzeczywistości mniej kodem niż implementacja ich jako metod dla każdego typu. Można się spierać o poszczególne przypadki, ale to część Pythona i już za późno na takie fundamentalne zmiany. Funkcje mają aby pozostać, aby uniknąć masowego łamania kodu.

Chociaż interesujące, powyższe nie mówi zbyt wiele o tym, jaką strategię przyjąć.

Jest to jeden z powodów - przy niestandardowych metodach programiści będą możliwość wyboru innej nazwy metody, np. getLength (), length(), getlength() lub w ogóle. Python wymusza ścisłe nazewnictwo, tak aby można użyć wspólnej funkcji len ().

Nieco więcej interesujące. Moim zdaniem funkcje są w pewnym sensie Pythoniczną wersją interfejsów.

Wreszcie, od samego Guido :

rozmowa o możliwościach / interfejsach skłoniła mnie do zastanowienia się nad niektórymi z naszych "rogue" specjalne nazwy metod. W odnośniku językowym jest napisane: "A klasa może zaimplementować pewne operacje, które są wywoływane przez specjalne składni (np. operacje arytmetyczne czy zapisywanie i krojenie) przez definiowanie metod z nazwy specjalne."Ale są wszystkie te metody z nazwami specjalnymi jak __len__ lub __unicode__, które wydają się być przewidziane z korzyścią dla wbudowanych funkcji, a nie dla obsługa składni. Przypuszczalnie w Pythonie opartym na interfejsie, te metody zamieniłyby się w metody o regularnych nazwach na ABC, tak aby __len__ stałoby się

class container:
  ...
  def len(self):
    raise NotImplemented

chociaż, myśląc o tym trochę więcej, nie widzę dlaczego wszystkie składniowe operacje nie tylko wywołują odpowiednia metoda na konkretne ABC. "<", na przykład, prawdopodobnie wywoła "object.lessthan "(a może"comparable.lessthan"). Więc inny korzyścią byłaby możliwość odciągnięcia Pythona od tego mangled-nazwa dziwności, która wydaje mi się ulepszeniem HCI .

Hm. Nie jestem pewien, czy się Zgadzam (rysunek, że: -).

Są dwa bity "uzasadnienia Pythona" , które chciałbym wyjaśnić najpierw.

Po pierwsze, wybrałem len (x) po x. len () z powodów HCI (def __len__() przyszedł znacznie później). Istnieją dwa splecione ze sobą powody, oba HCI:

(a) w przypadku niektórych operacji zapis przedrostkowy czyta się lepiej niż postfix -- prefix (i infix!) działania mają długą tradycję w matematyka, która lubi notacje, gdzie wizualizacje pomagają matematyk myśli o problemie. Porównaj łatwość z jaką my przepisać formułę typu x*(a+b) na x*a + x*b do niezgrabności robienie tego samego przy użyciu surowego OO notacja.

(b) kiedy czytam kod mówiący len(x)i wiem że prosi o długość czegoś. To mówi mi dwie rzeczy: rezultatem jest integer, a argument jest jakimś kontenerem. Wręcz przeciwnie, kiedy czytam x.len(), muszę już wiedzieć, że x jest jakimś kontener implementujący interfejs lub dziedziczący z klasy, która posiada standard len(). Świadkami zamieszania, które czasami mamy, gdy klasa, która nie implementuje odwzorowanie ma get() lub keys() metoda, lub coś, co nie jest plikiem, ma metodę write().

Mówiąc to samo w inny sposób, widzę "len" jako wbudowany operacja . Nie chciałbym tego stracić. Nie mogę powiedzieć na pewno, czy miałeś to na myśli, czy nie, ale 'def len(self): ...'z pewnością brzmi jak ty chcesz go zdegradować do zwykłej metody. Jestem zdecydowanie -1 na tym.

Druga część Pythona, którą obiecałem wyjaśnić, jest powodem dlaczego wybrałem specjalne metody szukania __special__ i nie tylko special. Przewidywałem wiele operacji, które klasy mogą chcieć aby zastąpić, niektóre standardowe (np. __add__ lub __getitem__), niektóre nie tak standard (np. pickle ' s __reduce__ przez długi czas nie miał wsparcia w C kod w ogóle). Nie chciałem, żeby te specjalne operacje używały zwykłych nazwy metod, bo wtedy już istniejące klasy, czyli klasy napisane przez użytkownicy bez pamięci encyklopedycznej dla wszystkich metod specjalnych, będzie narażony na przypadkowe definiować operacje, których nie chcieli wdrożenie, co może mieć katastrofalne konsekwencje. Ivan Krstić wyjaśnił to bardziej zwięźle w swojej wiadomości, która dotarła po tym, jak spisałem to wszystko.

-- --Guido van Rossum (Strona domowa: http://www.python.org ~ guido/)

Rozumiem to, że w niektórych przypadkach zapis przedrostkowy ma po prostu większy sens(tj.Kwak ma więcej sensu niż Kwak(kaczka) z językowego punktu widzenia.) i ponownie funkcje pozwalają na "Interfejsy".

W takim przypadku moim zdaniem implementacja get_king_moves opierałaby się wyłącznie na pierwszym punkcie 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 domyślam się funkcji, ponieważ naprawdę chcę zasygnalizować interfejs push-pop)

TLDR: Czy ktos moze wyjasnic jaka jest strategia decydowania kiedy uzywac funkcji vs. metody powinny być?

Author: Ceasar Bautista, 2011-11-13

6 answers

Moja ogólna zasada jest taka - czy operacja jest wykonywana na obiekcie czy przez obiekt?

Jeśli jest wykonywana przez obiekt, powinna to być operacja member. Jeśli może odnosić się również do innych rzeczy lub jest robione przez coś innego do obiektu, to powinna to być funkcja (lub być może członek czegoś innego).

Przy wprowadzaniu programowania tradycyjnym (aczkolwiek błędnym) jest opisywanie obiektów w kategoriach rzeczywistych obiektów, takich jak samochody. Wspominasz kaczka, więc zróbmy to.

class duck: 
    def __init__(self):pass
    def eat(self, o): pass 
    def crap(self) : pass
    def die(self)
    ....

W kontekście analogii "obiekty są rzeczywistymi rzeczami", "poprawne" jest dodanie metody klasowej dla wszystkiego, co obiekt może zrobić. Więc powiedz, że chcę zabić kaczkę, czy dodam .zabić kaczkę? Nie... z tego, co wiem, zwierzęta nie popełniają samobójstw. Dlatego jeśli chcę zabić kaczkę, powinienem to zrobić:

def kill(o):
    if isinstance(o, duck):
        o.die()
    elif isinstance(o, dog):
        print "WHY????"
        o.die()
    elif isinstance(o, nyancat):
        raise Exception("NYAN "*9001)
    else:
       print "can't kill it."
[[2]}odchodząc od tej analogii, dlaczego używamy metod i klas? Ponieważ chcemy zawierać dane i mamy nadzieję, że uporządkujemy nasze kod w taki sposób, aby w przyszłości mógł być wielokrotnego użytku i rozszerzalny. To prowadzi nas do pojęcia enkapsulacji, które jest tak drogie OO design.

Zasada enkapsulacji jest tym, do czego to się sprowadza: jako projektant powinieneś ukrywać wszystko o implementacji i klasach wewnętrznych, do których dostęp nie jest absolutnie konieczny dla każdego użytkownika lub innego dewelopera. Ponieważ mamy do czynienia z instancjami klas, sprowadza się to do "jakie operacje są kluczowe na tym instancja ". Jeśli operacja nie jest specyficzna dla instancji, to nie powinna być funkcją członkowską.

TL; DR : co powiedział @ Bryan. Jeśli działa na instancji i potrzebuje dostępu do danych wewnętrznych instancji klasy, powinna to być funkcja członkowska.

 65
Author: arrdem,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-17 02:32:54

Użyj klasy, gdy chcesz:

1) odizoluj kod wywołujący od szczegółów implementacji -- korzystając z abstrakcji i enkapsulacji .

2) Gdy chcesz być substytutem dla innych obiektów-wykorzystując polimorfizm .

3) Gdy chcesz ponownie użyć kodu dla podobnych obiektów -- korzystając z dziedziczenia .

Użyj funkcji do wywołań, które mają sens w wielu różnych typach obiektów -- dla przykład, wbudowany len oraz repr funkcje mają zastosowanie do wielu rodzajów obiektów.

To powiedziawszy, wybór czasami sprowadza się do kwestii gustu. Pomyśl w kategoriach tego, co jest najbardziej wygodne i czytelne dla typowych połączeń. Na przykład, który byłby lepszy (x.sin()**2 + y.cos()**2).sqrt() Czy sqrt(sin(x)**2 + cos(y)**2)?
 23
Author: Raymond Hettinger,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-13 19:04:16

Oto prosta zasada: jeśli kod działa na pojedynczą instancję obiektu, użyj metody. Nawet lepiej: użyj metody, chyba że istnieje przekonujący powód, aby napisać ją jako funkcję.

W twoim konkretnym przykładzie chcesz, aby wyglądał tak:

chessboard = Chessboard()
...
chessboard.get_king_moves()
Nie myśl o tym. Zawsze używaj metod, dopóki nie dojdzie do punktu, w którym powiesz sobie: "nie ma sensu, aby uczynić tę metodę", w którym to przypadku możesz utworzyć funkcję.
 7
Author: Bryan Oakley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-13 00:58:30

Zazwyczaj myślę o czymś takim jak osoba.

Atrybutami są Imię, wzrost, rozmiar buta itp.

Metody i Funkcje są operacjami, które osoba może wykonać.

Jeśli operacja może być wykonana przez dowolną starą osobę, nie wymagającą niczego unikalnego dla tej konkretnej osoby (i nie zmieniając niczego na tej konkretnej osobie), to jest to Funkcja i powinna być zapisana jako taka.

Jeśli operacja to działanie na osobę (np. jedzenie, chodzenie,...) lub wymaga czegoś wyjątkowego dla tej osoby, aby zaangażować się (jak taniec, pisanie książki, ...), wówczas powinna to być Metoda .

Oczywiście nie zawsze jest trywialne przełożenie tego na konkretny obiekt, z którym pracujesz, ale uważam, że jest to dobry sposób na myślenie o tym.

 7
Author: Thriveth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-23 12:41:16

Generalnie używam klas do implementacji logicznego zestawu możliwości dla jakiejś Rzeczy, tak że w pozostałej części mojego programu mogę rozumować o Rzeczy, nie martwiąc się o wszystkie małe obawy, które składają się na jego implementację.

Wszystko, co jest częścią tej podstawowej abstrakcji "co możesz zrobić z rzeczą", powinno być zazwyczaj metodą. Zazwyczaj obejmuje to wszystko, co może zmienić Rzecz , ponieważ wewnętrzny stan danych jest zwykle uważany za prywatny i nie będący częścią logicznej idei " co można zrobić z rzeczą".

Kiedy przychodzi się do operacji wyższego poziomu, zwłaszcza jeśli dotyczą one Wielu Rzeczy, to zazwyczaj są one najbardziej naturalnie wyrażane jako funkcje, jeśli mogą być zbudowane z publicznej abstrakcji Rzeczy bez potrzeby specjalnego dostępu do wewnętrznych (chyba że są to metody jakiegoś innego obiektu). Ma to tę wielką zaletę, że kiedy zdecyduję się całkowicie przepisać wewnętrzne jak działa moja Rzecz (bez zmiany interfejsu), mam tylko mały podstawowy zestaw metod do przepisania, a potem wszystkie zewnętrzne funkcje napisane w kategoriach tych metod będą po prostu działać. Uważam, że naleganie, aby wszystkie operacje związane z klasą X były metodami na klasie X prowadzi do zbyt skomplikowanych klas.

To zależy od kodu, który piszę. Dla niektórych programów modeluję je jako zbiór obiektów, których interakcje powodują zachowanie programu; tutaj najważniejsza funkcjonalność jest ściśle powiązana z pojedynczym obiektem, a więc jest zaimplementowana w metodach, z rozproszeniem funkcji użytkowych. Dla innych programów najważniejszą rzeczą jest zestaw funkcji, które manipulują danymi, A klasy są używane tylko do implementacji naturalnych "typów kaczek", które są manipulowane przez funkcje.

 4
Author: Ben,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-13 03:02:23

Można powiedzieć, że "w obliczu dwuznaczności Odrzuć pokusę zgadywania".

Jednak to nawet nie jest zgadywanka. Masz całkowitą pewność, że wyniki obu podejść są takie same, ponieważ rozwiązują twój problem.

Uważam, że dobrze jest mieć wiele sposobów na osiągnięcie celów. Chciałbym pokornie powiedzieć, jak inni użytkownicy już, aby zatrudnić cokolwiek "smakuje lepiej" / czuje się bardziej intuicyjny , jeśli chodzi o język.

 0
Author: João Silva,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-12 10:28:10