Wiem, że Python nie obsługuje przeciążania metod, ale napotkałem problem, którego nie potrafię rozwiązać w przyjemny Python.
Tworzę grę, w której postać musi strzelać różnymi kulami, ale jak napisać różne funkcje do tworzenia tych kul? Załóżmy na przykład, że mam funkcję, która tworzy pocisk przemieszczający się z punktu A do B z określoną prędkością. Napiszę taką funkcję:
def add_bullet(sprite, start, headto, speed):
... Code ...
Ale chcę napisać inne funkcje do tworzenia pocisków, takie jak:
def add_bullet(sprite, start, direction, speed):
def add_bullet(sprite, start, headto, spead, acceleration):
def add_bullet(sprite, script): # For bullets that are controlled by a script
def add_bullet(sprite, curve, speed): # for bullets with curved paths
... And so on ...
I tak z wieloma odmianami. Czy jest lepszy sposób, aby to zrobić bez użycia tak wielu argumentów słowa kluczowego, że robi się trochę brzydko szybko. Zmiana nazwy poszczególnych funkcji jest bardzo złe zbyt, ponieważ można uzyskać albo add_bullet1
, add_bullet2
albo add_bullet_with_really_long_name
.
Aby odpowiedzieć na niektóre odpowiedzi:
Nie, nie mogę utworzyć hierarchii klas Bullet, ponieważ jest to zbyt wolne. Rzeczywisty kod do zarządzania punktorami znajduje się w C, a moje funkcje są otoczone przez C API.
Wiem o argumentach słów kluczowych, ale sprawdzanie wszelkiego rodzaju kombinacji parametrów staje się denerwujące, ale domyślne argumenty pomagają przydzielić
acceleration=0
default value + if + else
tego samego co C ++. Jest to jedna z niewielu rzeczy, które C ++ ma lepszą czytelność niż Python ...script, curve
, czy mają wspólnego przodka, jakie metody obsługują. W przypadku pisania typu „kaczor” to od Ciebie zależy, czy projekt klasowy obliczy, jakie metody muszą obsługiwać. PrzypuszczalnieScript
obsługuje pewnego rodzaju wywołanie zwrotne oparte na taktowaniu czasu (ale jaki obiekt powinien zwrócić? Pozycję w tym czasie? Trajektorię w tym czasie?). Prawdopodobniestart, direction, speed
istart, headto, spead, acceleration
oba opisują rodzaje trajektorii, ale znowu to od Ciebie zależy zaprojektowanie klasy odbierającej, aby wiedziała, jak ją rozpakować i przetworzyć.Odpowiedzi:
To, o co prosisz, nazywa się wysyłką wielokrotną . Zobacz przykłady języków Julii , które pokazują różne rodzaje wysyłek.
Jednak zanim się na to spojrzymy, najpierw zastanowimy się, dlaczego przeciążenie nie jest tak naprawdę tym, czego chcesz w Pythonie.
Dlaczego nie przeciążenie?
Po pierwsze, należy zrozumieć pojęcie przeciążenia i dlaczego nie ma ono zastosowania do Pythona.
Python jest językiem dynamicznie typowanym, więc koncepcja przeciążenia po prostu go nie dotyczy. Jednak nie wszystko stracone, ponieważ możemy tworzyć takie alternatywne funkcje w czasie wykonywania:
Powinniśmy więc być w stanie wykonywać multimetody w Pythonie - lub, jak to się nazywa alternatywnie: wielokrotne wysyłanie .
Wielokrotna wysyłka
Multimetody są również nazywane wysyłaniem wielokrotnym :
Python nie obsługuje tego po wyjęciu z pudełka 1 , ale tak się składa, że istnieje doskonały pakiet Pythona o nazwie multipledispatch , który właśnie to robi.
Rozwiązanie
Oto, w jaki sposób możemy wykorzystać pakiet multipledispatch 2 do wdrożenia twoich metod:
1. Python 3 obsługuje obecnie pojedynczą wysyłkę.
2. Uważaj, aby nie używać multipledispatch w środowisku wielowątkowym, bo dostaniesz dziwne zachowanie.
źródło
speed
może zmienić się w środku funkcji, gdy inny wątek ustawia własną wartośćspeed
) !!! Długo mi zajęło uświadomienie sobie, że winowajcą była biblioteka.Python obsługuje „przeciążanie metod” w trakcie prezentacji. W rzeczywistości to, co właśnie opisujesz, jest trywialne do zaimplementowania w Pythonie na tak wiele różnych sposobów, ale wybrałbym:
W powyższym kodzie
default
jest prawdopodobną wartością domyślną dla tych argumentów lubNone
. Następnie możesz wywołać metodę tylko z argumentami, które Cię interesują, a Python użyje wartości domyślnych.Możesz także zrobić coś takiego:
Inną alternatywą jest bezpośrednie połączenie żądanej funkcji bezpośrednio z klasą lub instancją:
Jeszcze innym sposobem jest użycie abstrakcyjnego wzorca fabrycznego:
źródło
if sprite and script and not start and not direction and not speed...
po prostu wiedzieć, że jest w określonej akcji. ponieważ dzwoniący może wywołać funkcję, podając wszystkie dostępne parametry. Podczas przeciążania określ dla Ciebie dokładne zestawy odpowiednich parametrów.Możesz użyć rozwiązania „roll-your-own” do przeciążania funkcji. Ten jest skopiowany z artykułu Guido van Rossuma o multimetodach (ponieważ istnieje niewielka różnica między mm a przeciążeniem w pythonie):
Wykorzystanie byłoby
Obecnie najbardziej restrykcyjnymi ograniczeniami są:
źródło
Możliwą opcją jest użycie modułu multipledispatch, jak opisano tutaj: http://matthewrocklin.com/blog/work/2014/02/25/Multiple-Dispatch
Zamiast tego:
Możesz to zrobić:
W wyniku użycia:
źródło
W Pythonie 3.4 dodano PEP-0443. Funkcje ogólne pojedynczej wysyłki .
Oto krótki opis API z PEP.
Aby zdefiniować funkcję ogólną, udekoruj ją dekoratorem @singledispatch. Zauważ, że wysyłka odbywa się na podstawie pierwszego argumentu. Utwórz odpowiednio swoją funkcję:
Aby dodać przeciążone implementacje do funkcji, użyj atrybutu register () funkcji ogólnej. To jest dekorator, przyjmujący parametr typu i dekorujący funkcję realizującą operację dla tego typu:
źródło
Ten typ zachowania zazwyczaj rozwiązuje się (w językach OOP) za pomocą polimorfizmu. Każdy rodzaj pocisku byłby odpowiedzialny za wiedzę o tym, jak się porusza. Na przykład:
Przekaż tyle argumentów do istniejącej funkcji c_, a następnie wykonaj zadanie określenia, która funkcja c ma zostać wywołana na podstawie wartości w początkowej funkcji c. Python powinien więc zawsze wywoływać tylko jedną funkcję c. Ta jedna funkcja c sprawdza argumenty, a następnie może odpowiednio delegować na inne funkcje c.
Zasadniczo używasz każdej podklasy jako innego kontenera danych, ale definiując wszystkie potencjalne argumenty klasy podstawowej, podklasy mogą zignorować te, z którymi nic nie robią.
Kiedy pojawia się nowy typ pocisku, możesz po prostu zdefiniować jeszcze jedną właściwość w bazie, zmienić funkcję jednego pytona, aby przekazywała dodatkową właściwość, i jedną funkcję c_funkcji, która odpowiednio analizuje argumenty i deleguje. Chyba nie brzmi to tak źle.
źródło
pos+v*t
a następnie porównywania do granic ekranuif x > 800
i tak dalej. Wywołanie tych funkcji kilkaset razy na klatkę okazało się niedopuszczalnie wolne. Było to coś w rodzaju 40 fps przy 100% procesora z czystym pytonem do 60 fps z 5% -10% po wykonaniu w C.add_bullet
i wyodrębnij wszystkie potrzebne pola. Zmienię swoją odpowiedź.Przez przechodzącą args słów kluczowych .
źródło
Użyj w definicji wielu argumentów słów kluczowych lub utwórz
Bullet
hierarchię, której instancje są przekazywane do funkcji.źródło
Myślę, że twoim podstawowym wymaganiem jest posiadanie składni podobnej do C / C ++ w pythonie przy możliwie najmniejszym bólu głowy. Chociaż podobała mi się odpowiedź Aleksandra Poluektowa, nie działa na zajęciach.
Dla klas powinny działać następujące elementy. Działa, rozróżniając liczbę argumentów niebędących słowami kluczowymi (ale nie obsługuje rozróżniania według typu):
I można go użyć w następujący sposób:
Wynik:
źródło
@overload
Dekorator dodano wskazówek typu (PEP 484). Chociaż nie zmienia to zachowania Pythona, ułatwia zrozumienie, co się dzieje, i umożliwia mypy wykrywanie błędów.Zobacz: Wskazówki dotyczące typów i PEP 484
źródło
Myślę, że droga do
Bullet
hierarchii klas z powiązanym polimorfizmem jest słuszna. Można skutecznie przeciążyć konstruktora klasy bazowej za pomocą metaklasy, tak aby wywołanie klasy bazowej spowodowało utworzenie odpowiedniego obiektu podklasy. Poniżej znajduje się przykładowy kod ilustrujący istotę tego, co mam na myśli.Zaktualizowano
Kod został zmodyfikowany, aby działał zarówno w Pythonie 2, jak i 3, aby był odpowiedni. Dokonano tego w sposób, który pozwala uniknąć użycia jawnej składni metaklasy Pythona, która różni się między dwiema wersjami.
Aby osiągnąć ten cel,
BulletMetaBase
instancjaBulletMeta
klasy jest tworzona przez jawne wywołanie metaklasy podczas tworzenia klasy podstawowejBullet
(zamiast używania__metaclass__=
atrybutu klasy lubmetaclass
argumentu słowa kluczowego w zależności od wersji Pythona).Wynik:
źródło
Bullet
podklas bez konieczności modyfikowania klasy podstawowej lub funkcji fabrycznej za każdym razem, gdy dodajesz inny podtyp. (Oczywiście, jeśli używasz C zamiast C ++, myślę, że nie masz klas.) Możesz również stworzyć mądrzejszą metaklasę, która samodzielnie wymyśliła, jaką podklasę utworzyć na podstawie typu i / lub liczby przekazanych argumentów (podobnie jak C ++ obsługuje przeciążanie).W Pythonie 3.8 dodano funkcję funkools.singledispatch
Wynik
Wynik:
źródło
Używaj argumentów słów kluczowych z wartościami domyślnymi. Na przykład
W przypadku pocisku prostego kontra zakrzywionego dodałbym dwie funkcje:
add_bullet_straight
iadd_bullet_curved
.źródło
przeciążanie metod jest trudne w Pythonie. Można jednak użyć przekazywania zmiennych, list lub prymitywnych zmiennych.
Próbowałem czegoś dla moich przypadków użycia, może to pomóc tutaj zrozumieć ludzi, którzy przeciążają metody.
Weźmy twój przykład:
metoda przeciążenia klasy z wywołaniem metod z innej klasy.
przekaż argumenty ze zdalnej klasy:
LUB
Obsługuje się więc obsługę list, słowników lub prymitywnych zmiennych z przeciążenia metody.
wypróbuj to dla swoich kodów.
źródło
Po prostu prosty dekorator
Możesz użyć tego w ten sposób
Zmodyfikuj go, aby dostosować go do swojego przypadku użycia.
self/this
argumentu. Jednak większość języków robi to tylko dlathis
argumentu. Powyższy dekorator rozszerza pomysł na wiele parametrów.Aby wyczyścić, załóż język statyczny i zdefiniuj funkcje
W przypadku wysyłki statycznej (przeciążenia) zobaczysz dwukrotnie „numer wywoływany”, ponieważ
x
został on zadeklarowany jakoNumber
i to wszystko oznacza przeciążenie. W przypadku dynamicznej wysyłki pojawi się „liczba całkowita nazywana, liczba zmiennoprzecinkowa”, ponieważ są to rzeczywiste typyx
w momencie wywołania funkcji.źródło
x
dla dynamicznej wysyłki, ani w jakiej kolejności obie metody zostały wywołane dla wysyłki statycznej. Polecam edytowanie drukowanych wyciągówprint('number called for Integer')
itp.