Chciałbym wiedzieć, czy można kontrolować definicję funkcji Pythona w oparciu o ustawienia globalne (np. System operacyjny). Przykład:
@linux
def my_callback(*args, **kwargs):
print("Doing something @ Linux")
return
@windows
def my_callback(*args, **kwargs):
print("Doing something @ Windows")
return
Następnie, jeśli ktoś używa Linuksa, zostanie zastosowana pierwsza definicja, my_callback
a druga zostanie po cichu zignorowana.
Nie chodzi o określenie systemu operacyjnego, ale o definicję funkcji / dekoratorów.
my_callback = windows(<actual function definition>)
- więc nazwamy_callback
zostanie zastąpiona, niezależnie od tego, co może zrobić dekorator. Jedynym sposobem, w jaki wersja Linux funkcji może znaleźć się w tej zmiennej, jestwindows()
jej zwrócenie - ale funkcja nie ma możliwości dowiedzenia się o wersji Linux. Myślę, że bardziej typowym sposobem osiągnięcia tego jest posiadanie definicji funkcji specyficznych dla systemu operacyjnego w osobnych plikach, a warunkowoimport
tylko jeden z nich.functools.singledispatch
, który robi coś podobnego do tego, co chcesz. Tamregister
dekorator wie o programie rozsyłającym (ponieważ jest to atrybut funkcji wysyłki i specyficzny dla tego konkretnego programu rozsyłającego), dzięki czemu może zwrócić program rozsyłający i uniknąć problemów z twoim podejściem.uuid.getnode()
. (To powiedziawszy, odpowiedź Todda tutaj jest całkiem dobra.)Odpowiedzi:
Jeśli celem jest uzyskanie takiego samego efektu w kodzie, jaki ma #ifdef WINDOWS / #endif .. oto sposób na zrobienie tego (jestem na komputerze Mac).
Prosta obudowa, bez łączenia
Dzięki tej implementacji otrzymujesz taką samą składnię, jaką masz w swoim pytaniu.
Zasadniczo to, co robi powyższy kod, polega na przypisaniu zulu do zulu, jeśli platforma pasuje. Jeśli platforma nie pasuje, zwróci Zulu, jeśli została wcześniej zdefiniowana. Jeśli nie został zdefiniowany, zwraca funkcję zastępczą, która wywołuje wyjątek.
Dekoratorzy są koncepcyjnie łatwi do zrozumienia, jeśli weźmiesz to pod uwagę
jest analogiczny do:
Oto implementacja wykorzystująca sparametryzowany dekorator:
Sparametryzowane dekoratory są analogiczne do
foo = mydecorator(param)(foo)
.Zaktualizowałem odpowiedź całkiem sporo. W odpowiedzi na komentarze rozszerzyłem swój pierwotny zakres o aplikacje do metod klasowych oraz o funkcje zdefiniowane w innych modułach. W tej ostatniej aktualizacji byłem w stanie znacznie zmniejszyć złożoność związaną z określeniem, czy funkcja została już zdefiniowana.
[Mała aktualizacja tutaj ... Po prostu nie mogłem tego odłożyć - to było zabawne ćwiczenie] Przeprowadziłem kilka testów tego i odkryłem, że działa ogólnie na wywołaniach - nie tylko zwykłych funkcjach; możesz również ozdobić deklaracje klasowe, czy to na żądanie, czy nie. I obsługuje wewnętrzne funkcje funkcji, więc takie rzeczy są możliwe (chociaż prawdopodobnie nie w dobrym stylu - to tylko kod testowy):
Powyżej pokazuje podstawowy mechanizm dekoratorów, jak uzyskać dostęp do zakresu dzwoniącego i jak uprościć wiele dekoratorów, które zachowują się podobnie, poprzez zdefiniowanie wewnętrznej funkcji zawierającej wspólny algorytm.
Łańcuchowe wsparcie
Aby wesprzeć tworzenie łańcuchów tych dekoratorów wskazujących, czy funkcja dotyczy więcej niż jednej platformy, dekorator można zaimplementować w następujący sposób:
W ten sposób wspierasz tworzenie łańcuchów:
źródło
macos
iwindows
są zdefiniowane w tym samym module cozulu
. Wierzę, że spowoduje to również pozostawienie funkcji tak,None
jakby funkcja nie została zdefiniowana dla bieżącej platformy, co doprowadziłoby do bardzo mylących błędów środowiska uruchomieniowego .Chociaż
@decorator
składnia wygląda ładnie, uzyskuje się dokładnie takie samo zachowanie, jak pożądane za pomocą prostegoif
.W razie potrzeby pozwala to również łatwo egzekwować, że niektóre przypadki pasują.
źródło
def callback_windows(...)
adef callback_linux(...)
potemif windows: callback = callback_windows
itd. Ale tak czy inaczej jest to o wiele łatwiejsze do odczytania, debugowania i utrzymania.elif
, ponieważ nigdy nie będzie to oczekiwany przypadek, że więcej niż jeden zlinux
/windows
/macOS
będzie prawdziwy. W rzeczywistości prawdopodobnie po prostu zdefiniowałbym jedną zmiennąp = platform.system()
, a następnie użyłbymif p == "Linux"
etc, zamiast wielu flag boolowskich. Zmienne, które nie istnieją, nie mogą zostać zsynchronizowane.elif
z pewnością ma swoje zalety - konkretnie, kroczącaelse
+raise
, aby upewnić się, że co najmniej jeden przypadek zrobił mecz. Jeśli chodzi o ocenę predykatu, wolę, aby były one wstępnie ocenione - pozwala to uniknąć powielania i definiuje i używa oddzielenia. Nawet jeśli wynik nie jest przechowywany w zmiennych, istnieją teraz zakodowane wartości, które mogą wyjść z synchronizacji tak samo. I może nigdy nie pamiętam różne ciągi magiczne dla różnych środków, npplatform.system() == "Windows"
kontrasys.platform == "win32"
...Enum
czy tylko z zestawem stałych.Poniżej znajduje się jedna możliwa implementacja tego mechanika. Jak zauważono w komentarzach, może być preferowane wdrożenie interfejsu „master dispatcher”, takiego jak ten widziany w
functools.singledispatch
, w celu śledzenia stanu związanego z wieloma przeciążonymi definicjami. Mam nadzieję, że ta implementacja zapewni przynajmniej wgląd w problemy, z którymi możesz mieć do czynienia podczas opracowywania tej funkcji dla większej bazy kodu.Testowałem tylko, że poniższa implementacja działa zgodnie ze specyfikacją w systemach Linux, więc nie mogę zagwarantować, że to rozwiązanie odpowiednio umożliwia tworzenie funkcji specjalistycznych dla platformy. Nie używaj tego kodu w środowisku produkcyjnym bez wcześniejszego dokładnego przetestowania go.
Aby użyć tego dekoratora, musimy przejść przez dwa poziomy pośredni. Najpierw musimy określić, na jaką platformę ma odpowiadać dekorator. Dokonuje się tego przez linię
implement_linux = implement_for_os('Linux')
i jej odpowiednik w oknie powyżej. Następnie musimy przekazać istniejącą definicję przeciążonej funkcji. Ten krok należy wykonać w miejscu definicji, jak pokazano poniżej.Aby zdefiniować funkcję specjalizowaną w platformie, możesz teraz napisać:
Połączenia z
some_function()
zostaną odpowiednio wysłane do podanej definicji specyficznej dla platformy.Osobiście nie radziłbym używać tej techniki w kodzie produkcyjnym. Moim zdaniem lepiej jest wyraźnie powiedzieć o zachowaniach zależnych od platformy w każdej lokalizacji, w której występują te różnice.
źródło
implement_for_os
nie zwraca samego dekoratora, ale zwraca funkcję, która wytworzy dekorator, gdy zostanie udostępniona poprzednia definicja danej funkcji.Napisałem swój kod, zanim przeczytałem inne odpowiedzi. Po skończeniu kodu znalazłem, że kod @ Todda jest najlepszą odpowiedzią. W każdym razie zamieszczam swoją odpowiedź, ponieważ czułem się dobrze podczas rozwiązywania tego problemu. Nauczyłem się nowych rzeczy dzięki temu dobremu pytaniu. Wadą mojego kodu jest to, że istnieje obciążenie związane z wyszukiwaniem słowników za każdym razem, gdy wywoływane są funkcje.
źródło
Czystym rozwiązaniem byłoby utworzenie rejestru funkcji dedykowanych, który będzie wysyłany dalej
sys.platform
. To jest bardzo podobne dofunctools.singledispatch
. Kod źródłowy tej funkcji stanowi dobry punkt wyjścia do implementacji niestandardowej wersji:Teraz można go używać podobnie do
singledispatch
:Rejestracja działa również bezpośrednio na nazwy funkcji:
źródło