Z grubsza partial
robi coś takiego (oprócz obsługi argumentów słów kluczowych itp.):
def partial(func, *part_args):
def wrapper(*extra_args):
args = list(part_args)
args.extend(extra_args)
return func(*args)
return wrapper
Tak więc, wywołując partial(sum2, 4)
, tworzysz nową funkcję (wywoływalną, aby być precyzyjnym), która zachowuje się podobnie sum2
, ale ma o jeden argument pozycyjny mniej. Ten brakujący argument jest zawsze zastępowany przez 4
, więcpartial(sum2, 4)(2) == sum2(4, 2)
Jeśli chodzi o powody, dla których jest to potrzebne, istnieje wiele przypadków. Na przykład załóżmy, że musisz przekazać funkcję gdzieś, gdzie oczekuje się, że będzie miała 2 argumenty:
class EventNotifier(object):
def __init__(self):
self._listeners = []
def add_listener(self, callback):
''' callback should accept two positional arguments, event and params '''
self._listeners.append(callback)
# ...
def notify(self, event, *params):
for f in self._listeners:
f(event, params)
Ale funkcja, którą już masz, potrzebuje dostępu do trzeciego context
obiektu, aby wykonać swoją pracę:
def log_event(context, event, params):
context.log_event("Something happened %s, %s", event, params)
Tak więc istnieje kilka rozwiązań:
Obiekt niestandardowy:
class Listener(object):
def __init__(self, context):
self._context = context
def __call__(self, event, params):
self._context.log_event("Something happened %s, %s", event, params)
notifier.add_listener(Listener(context))
Lambda:
log_listener = lambda event, params: log_event(context, event, params)
notifier.add_listener(log_listener)
Z częściami:
context = get_context() # whatever
notifier.add_listener(partial(log_event, context))
Z tych trzech partial
jest najkrótszy i najszybszy. (Jednak w przypadku bardziej złożonego przypadku możesz potrzebować obiektu niestandardowego).
extra_args
zmiennąextra_args
jest czymś, co zostało przekazane przez częściowego wywołującego, w przykładzie zp = partial(func, 1); f(2, 3, 4)
nim jest(2, 3, 4)
.callback
amy_callback
części niepełne są niezwykle przydatne.
Na przykład w sekwencji wywołań funkcji wyłożonych pionową kreską (w której wartość zwracana z jednej funkcji jest argumentem przekazywanym do następnej).
Czasami funkcja w takim potoku wymaga pojedynczego argumentu , ale funkcja bezpośrednio przed nią zwraca dwie wartości .
W tym scenariuszu
functools.partial
może pozwolić na zachowanie tego potoku funkcji w stanie nienaruszonym.Oto konkretny, odizolowany przykład: przypuśćmy, że chcesz posortować dane według odległości każdego punktu danych od jakiegoś celu:
Aby posortować te dane według odległości od celu, chciałbyś oczywiście zrobić to:
ale nie możesz - parametr key metody sort akceptuje tylko funkcje, które przyjmują jeden argument.
więc przepisz ponownie
euclid_dist
jako funkcję pobierającą pojedynczy parametr:p_euclid_dist
teraz przyjmuje pojedynczy argument,więc teraz możesz sortować swoje dane, przekazując funkcję częściową dla argumentu klucza metody sort:
Lub na przykład jeden z argumentów funkcji zmienia się w pętli zewnętrznej, ale jest ustalany podczas iteracji w pętli wewnętrznej. Używając częściowej, nie musisz przekazywać dodatkowego parametru podczas iteracji wewnętrznej pętli, ponieważ zmodyfikowana (częściowa) funkcja tego nie wymaga.
utwórz funkcję częściową (używając słowa kluczowego arg)
możesz również utworzyć funkcję częściową z argumentem pozycyjnym
ale to zwróci (np. utworzenie częściowego argumentu słowa kluczowego, a następnie wywołanie za pomocą argumentów pozycyjnych)
inny przypadek użycia: pisanie rozproszonego kodu przy użyciu
multiprocessing
biblioteki Pythona . Pula procesów jest tworzona metodą Pool:Pool
ma metodę mapowania, ale wymaga tylko jednej iteracji, więc jeśli chcesz przekazać funkcję z dłuższą listą parametrów, ponownie zdefiniuj funkcję jako częściową, aby naprawić wszystkie oprócz jednej:źródło
krótka odpowiedź,
partial
podaje domyślne wartości parametrów funkcji, które w innym przypadku nie miałyby wartości domyślnych.źródło
partial
i tak dalejCzęściowe mogą być używane do tworzenia nowych funkcji pochodnych, które mają wstępnie przypisane pewne parametry wejściowe
Aby zobaczyć rzeczywiste wykorzystanie części składowych, zapoznaj się z tym naprawdę dobrym wpisem na blogu:
http://chriskiehl.com/article/Cleaner-coding-through-partually-applied-functions/
Prosty, ale zgrabny przykład dla początkujących z bloga, pokazuje, jak można użyć
partial
on,re.search
aby uczynić kod bardziej czytelnym.re.search
podpis metody to:Stosując
partial
, możemy stworzyć wiele wersji wyrażenia regularnego,search
które będą odpowiadały naszym wymaganiom, na przykład:Teraz
is_spaced_apart
iis_grouped_together
są dwiema nowymi funkcjami pochodzącymi zre.search
, do którychpattern
zastosowano argument (ponieważpattern
jest to pierwszy argument wre.search
sygnaturze metody).Sygnatura tych dwóch nowych funkcji (wywoływalnych) to:
Oto jak możesz następnie użyć tych częściowych funkcji na jakimś tekście:
Możesz odnieść się do powyższego linku, aby uzyskać bardziej szczegółowe zrozumienie tematu, ponieważ obejmuje on ten konkretny przykład i wiele więcej.
źródło
is_spaced_apart = re.compile('[a-zA-Z]\s\=').search
? Jeśli tak, czy istnieje gwarancja, żepartial
idiom skompiluje wyrażenie regularne w celu szybszego ponownego użycia?Moim zdaniem to sposób na zaimplementowanie curry w Pythonie.
Wynik to 3 i 4.
źródło
Warto również wspomnieć, że gdy funkcja częściowa przekazuje inną funkcję, w której chcemy „zakodować” niektóre parametry, powinien to być parametr znajdujący się najbardziej po prawej stronie
ale jeśli zrobimy to samo, ale zamiast tego zmieniamy parametr
zwróci błąd, "TypeError: func () ma wiele wartości dla argumentu 'a'"
źródło
prt=partial(func, 7)
Ta odpowiedź jest bardziej przykładowym kodem. Wszystkie powyższe odpowiedzi dobrze wyjaśniają, dlaczego należy używać częściowych. Podam moje obserwacje i wykorzystam przypadki o częściowej.
Wynik powyższego kodu powinien wyglądać następująco:
Zauważ, że w powyższym przykładzie zostało zwrócone nowe wywołanie, które przyjmie parametr (c) jako argument. Zauważ, że jest to również ostatni argument funkcji.
Wynik powyższego kodu to również:
Zauważ, że * został użyty do rozpakowania argumentów niebędących słowami kluczowymi, a zwracana wartość wywoływana w zakresie tego, który argument może przyjąć, jest taka sama jak powyżej.
Inna obserwacja: Poniższy przykład pokazuje, że częściowe zwraca wywoływalny parametr, który przyjmie niezadeklarowany parametr (a) jako argument.
Wynik powyższego kodu powinien wyglądać następująco:
Podobnie,
Nadrukowany kod
Musiałem go użyć, gdy korzystałem
Pool.map_async
z metody zmultiprocessing
modułu. Możesz przekazać tylko jeden argument do funkcji roboczej, więc musiałem użyć,partial
aby moja funkcja robocza wyglądała jak wywoływalna z tylko jednym argumentem wejściowym, ale w rzeczywistości moja funkcja robocza miała wiele argumentów wejściowych.źródło