11/05/2026
W świecie programowania, zwłaszcza tego funkcyjnego, istnieją techniki, które mogą znacząco usprawnić sposób pisania kodu, czyniąc go bardziej modułowym, czytelnym i łatwiejszym do ponownego wykorzystania. Jedną z takich technik jest curryowanie. Chociaż nazwa może kojarzyć się z aromatyczną przyprawą, w kontekście informatyki odnosi się do potężnego konceptu, który pozwala na rozkładanie złożonych operacji na mniejsze, zarządzalne kroki. W tym artykule zagłębimy się w to, czym jest curryowanie w Pythonie, skąd pochodzi jego nazwa, jak je implementować i w jakich scenariuszach może okazać się niezwykle przydatne.

Co to jest curryowanie?
Curryowanie to technika w matematyce i informatyce, która polega na przekształceniu funkcji przyjmującej wiele argumentów w sekwencję funkcji, z których każda przyjmuje tylko jeden argument. Innymi słowy, zamiast wywoływać funkcję f(a, b, c), wywołujemy ją jako f_curried(a)(b)(c). Każde wywołanie funkcji z jednym argumentem zwraca nową funkcję, która czeka na kolejny argument, aż do momentu, gdy wszystkie argumenty zostaną dostarczone i zostanie obliczony ostateczny wynik.
Technika ta jest również uważana za wzorzec projektowy i często wykorzystywana jest w teoretycznych naukach komputerowych, ponieważ ułatwia przekształcanie modeli z wieloma argumentami w modele z pojedynczymi argumentami. Pozwala to na większą elastyczność i możliwość tworzenia bardziej wyspecjalizowanych funkcji na podstawie ogólnych.
Pochodzenie nazwy "Curry"
Jak wspomniano wcześniej, nazwa „curryowanie” nie ma nic wspólnego z popularną indyjską przyprawą, choć może sprawić, że Twój kod w Pythonie stanie się bardziej „pikantny” i interesujący. Nazwa ta jest hołdem dla logika i matematyka Haskella Brooksa Curry’ego, który szeroko wykorzystywał tę koncepcję. Ciekawostką jest, że sama idea pojawiła się już sześć lat wcześniej u Mojżesza Schönfinkela, dlatego czasem spotyka się również (choć rzadko) termin „Schönfinkelizacja”. Korzenie tego konceptu sięgają nawet końca XIX wieku i prac matematyka Gottloba Fregego.
Curryowanie funkcji w praktyce
Aby lepiej zrozumieć, jak działa curryowanie, przyjrzyjmy się przykładowi matematycznemu, a następnie jego implementacji w Pythonie. Jeśli mamy funkcję f, która przyjmuje n argumentów, możemy ją „zastąpić” kompozycją n funkcji f_1, f_2, ..., f_n, gdzie każda przyjmuje tylko jeden argument. Zamiast:
let x = f(a1, a2, a3)uzyskamy tę samą wartość x, jeśli wywołamy:
f_2 = f_1(a1) f_3 = f_2(a2) x = f_3(a3)Przykład w Pythonie z zagnieżdżonymi funkcjami
Oto jak można zaimplementować to w Pythonie, używając zagnieżdżonych funkcji:
def f(a1, a2, a3): return a1 * a2 * a3 def f1(a1): def f2(a2): def f3(a3): return f(a1, a2, a3) return f3 return f2 for i in range(1, 10): print(f(i, i + 1, i + 2), f1(i)(i + 1)(i + 2))Wynik działania tego kodu będzie identyczny dla obu sposobów wywołania funkcji, co potwierdza, że curryowanie dostarcza ten sam rezultat, ale w innej, sekwencyjnej formie:
6 6 24 24 60 60 120 120 210 210 336 336 504 504 720 720 990 990Przykład z BMI
Rozważmy również funkcję obliczającą BMI (Body Mass Index):
def BMI(weight, height): return weight / height ** 2 def bmi_curried(height): def bmi_weight(weight): return BMI(weight, height) return bmi_weight print(bmi_curried(1.76)(72))Wynik:
23.243801652892564W tym przykładzie bmi_curried(1.76) zwraca nową funkcję, która „zapamiętuje” wysokość 1.76. Następnie tę nową funkcję wywołujemy z wagą 72, aby uzyskać ostateczny wynik BMI.
Częściowe aplikowanie funkcji a curryowanie: `functools.partial`
W Pythonie moduł functools oferuje funkcję partial, która pozwala na tworzenie częściowo zaaplikowanych funkcji. Częściowe aplikowanie funkcji to technika, w której tworzymy nową funkcję z istniejącej, poprzez ustalenie z góry określonej liczby argumentów. Wynikiem jest funkcja, którą można wywołać z pozostałymi argumentami. Jest to ściśle związane z curryowaniem, ale nie jest tym samym. Curryowanie zawsze przekształca funkcję w sekwencję funkcji jednoargumentowych, natomiast częściowe aplikowanie może ustalić dowolną liczbę argumentów, a pozostałe mogą być nadal przekazywane jako wiele argumentów.
Oto jak działa partial:
from functools import partial def f(a1, a2): return a1 * a2 print(partial(f, 3)(4)) # Wynik: 12 def f_three_args(a1, a2, a3): return a1 * a2 * a3 print(partial(f_three_args, 3, 2)(4)) # Wynik: 24 print(partial(f_three_args, 3)(2, 4)) # Wynik: 24 print(partial(partial(f_three_args, 3), 2)(4)) # Wynik: 24partial jest bardzo przydatne, gdy chcemy stworzyć bardziej specyficzną wersję ogólnej funkcji, „zamrażając” niektóre z jej argumentów.
Tworzenie własnego dekoratora do curryowania
Aby ułatwić curryowanie dowolnych funkcji, możemy stworzyć własny dekorator. Dekorator to funkcja wyższego rzędu, która modyfikuje zachowanie innej funkcji. Nasz dekorator curry będzie przekształcał zwykłą funkcję w funkcję curryowaną, zwracając nową funkcję dla każdego dostarczonego argumentu, co pozwala na stopniowe aplikowanie argumentów.
def curry(func): def curried(*args): if len(args) == func.__code__.co_argcount: return func(*args) else: return lambda x: curried(*(args + (x,))) return curried @curry def prod3(x, y, z): return x + y + z print(prod3(3)(4)(5)) # Output: # (3,) # (3, 4) # (3, 4, 5) # 12W tym dekoratorze kluczowe jest sprawdzenie liczby argumentów. Atrybut func.__code__.co_argcount zwraca liczbę oczekiwanych argumentów pozycyjnych przez oryginalną funkcję. Jeśli liczba dostarczonych argumentów args zgadza się z oczekiwaną liczbą, wywołujemy oryginalną funkcję. W przeciwnym razie zwracamy funkcję lambda, która przyjmuje kolejny argument x i wywołuje curried z dotychczasowymi argumentami powiększonymi o x. Pozwala to na budowanie łańcucha funkcji jednoargumentowych.
Praktyczne zastosowania curryowania: Przykład koszyka zakupów
Curryowanie może być niezwykle użyteczne w scenariuszach, gdzie argumenty funkcji stają się dostępne stopniowo lub gdy chcemy tworzyć wyspecjalizowane wersje funkcji. Rozważmy przykład obliczania całkowitego kosztu koszyka zakupów z możliwością stosowania różnych rabatów na różnych etapach.
Obliczanie kosztu pojedynczego przedmiotu
Najpierw zdefiniujmy curryowaną funkcję do obliczania kosztu pojedynczego przedmiotu:
@curry def calculate_item_cost(price, quantity): return price * quantityStosowanie rabatów
Następnie tworzymy funkcję do stosowania rabatu procentowego. Ta funkcja przyjmuje procent i zwraca curryowaną funkcję:
@curry def subtract_percentage(discount_percentage, cost): discount_amount = (cost * discount_percentage) / 100 return cost - discount_amountWarto wspomnieć, że w świecie biznesu istnieją subtelne różnice między rabatami (discounts) a upustami/rebatami (rebates). Rabaty zazwyczaj oferują natychmiastową oszczędność w momencie zakupu, podczas gdy upusty oferują oszczędność po dokonaniu zakupu, pod warunkiem spełnienia określonych kryteriów. Obie strategie wpływają na zachowanie klienta, ale są stosowane w różnych scenariuszach. W naszym przykładzie będziemy traktować je jako kolejne obniżki procentowe.

Obliczanie całkowitego kosztu zamówienia
Teraz użyjmy tych curryowanych funkcji do obliczenia całkowitego kosztu zamówienia klienta:
# Obliczanie kosztu pojedynczych przedmiotów item1 = calculate_item_cost(10)(2) # Przedmiot 1 kosztuje 10€, ilość 2 item2 = calculate_item_cost(5)(3) # Przedmiot 2 kosztuje 5€, ilość 3 print(f"item1={item1}, item2={item2}") # Stosowanie rabatów procentowych calculate_discounted_price = subtract_percentage(12) calculate_rebated_price = subtract_percentage(3) # Obliczanie całkowitego kosztu z rabatami total_cost = calculate_rebated_price(calculate_discounted_price(item1 + item2)) print(f"Total cost: €{total_cost:.2f}")Wynik:
item1=20, item2=15 Total cost: €29.88Możemy również zastosować rabaty bezpośrednio na każdym przedmiocie lub w bardziej złożonej strukturze:
# Bezpośrednie obliczenie dla pojedynczego przedmiotu print(subtract_percentage(3)(subtract_percentage(12)(calculate_item_cost(10)(2)))) # Wynik: 17.072000000000003 # Przykład z listą zakupów (ilość, cena, rabat, upust) shopping_list = [(2, 10, 12, 3), (3, 5, 12, 3)] total = 0 sub_perc = subtract_percentage # krótsza nazwa for quantity, price, discount, rebate in shopping_list: subtotal = sub_perc(rebate)(sub_perc(discount)((calculate_item_cost(price)(quantity)))) total += subtotal print(f"Total cost: €{total:.2f}")Wynik:
Total cost: €29.88Ten przykład pokazuje, jak curryowanie pozwala na tworzenie elastycznych i komponowalnych funkcji do obsługi złożonych logik biznesowych, takich jak obliczenia cen i rabatów.
Curryowanie funkcji z dowolną liczbą argumentów
Jedno z interesujących pytań dotyczy tego, jak curryować funkcję z nieznaną i dowolną liczbą parametrów. Możemy użyć zagnieżdżonej funkcji, aby „akumulować” argumenty. Potrzebujemy sposobu, aby poinformować funkcję, kiedy ma zakończyć curryowanie i zwrócić ostateczną wartość. W tym celu możemy zastosować konwencję wywołania funkcji bez żadnych argumentów jako sygnału do obliczenia i zwrócenia wyniku.
def arimean(*args): return sum(args) / len(args) def curry(func): f_args, f_kwargs = [], {} def f(*args, **kwargs): nonlocal f_args, f_kwargs if args or kwargs: f_args += args f_kwargs.update(kwargs) return f else: result = func(*f_args, **f_kwargs) f_args, f_kwargs = [], {} # Resetowanie akumulatorów return result return f curried_arimean = curry(arimean) # Akumulacja argumentów curried_arimean(2)(5)(9)(5) # Wywołanie bez argumentów, aby uzyskać wynik print(curried_arimean()) # Wynik: 5.25 # Bez curryowania dla porównania print(arimean(2, 5, 9, 5)) # Wynik: 5.25Ta technika, choć nie jest „czystym” curryowaniem w ścisłym sensie (ponieważ wymaga dodatkowego wywołania bez argumentów), jest praktycznym sposobem na obsługę funkcji z *args i **kwargs.
Implementacja curryowania jako klasy
Możemy również zaimplementować to podejście za pomocą klasy, wykorzystując metodę __call__, która pozwala instancjom obiektów zachowywać się jak funkcje:
def arimean(*args): return sum(args) / len(args) class Curry: def __init__(self, func, *args): self.func = func self.args = args def __call__(self, *args): if not args: # Jeśli brak argumentów, oblicz wynik return self.func(*self.args) # W przeciwnym razie, zwróć nową instancję Curry z dodanymi argumentami return Curry(self.func, *(self.args + args)) curried_arimean = Curry(arimean) result = curried_arimean(2)(5)(9)(7)(5, 9)() # Ostatnie () jest kluczowe print("Result:", result) # Wynik: 6.166666666666667W tym podejściu, każde wywołanie instancji Curry z argumentami zwraca nową instancję Curry z zaktualizowaną listą zgromadzonych argumentów. Dopiero wywołanie bez argumentów (()) powoduje wykonanie oryginalnej funkcji z wszystkimi zgromadzonymi argumentami.
Kiedy używać curryowania?
Curryowanie, choć na pierwszy rzut oka może wydawać się skomplikowane, oferuje szereg korzyści, które mogą usprawnić Twój kod:
- Poprawa czytelności i ponownego użycia kodu: Curryowanie pozwala na tworzenie bardziej wyspecjalizowanych funkcji z ogólnych, co ułatwia ich rozumienie i ponowne wykorzystanie w różnych kontekstach. Możemy „prekonfigurować” funkcje dla konkretnych zastosowań.
- Łatwiejsza kompozycja funkcji: Ponieważ curryowane funkcje przyjmują tylko jeden argument na raz i zwracają inne funkcje, stają się idealnymi kandydatami do kompozycji funkcji, czyli łączenia mniejszych funkcji w większe, bardziej złożone operacje.
- Obsługa wywołań zwrotnych (callbacks) i obsługi zdarzeń: W scenariuszach, gdzie argumenty funkcji są dostarczane asynchronicznie lub stopniowo (np. w interfejsach użytkownika, gdzie dane przychodzą z różnych źródeł), curryowanie może uprościć zarządzanie stanem.
- Zmniejszenie ilości boilerplate code: Tworząc wyspecjalizowane funkcje za pomocą curryowania, możemy unikać pisania wielu podobnych funkcji, które różnią się tylko wartościami niektórych argumentów.
Tabela porównawcza: Bez curryowania vs. Z curryowaniem
Aby lepiej zobrazować różnice, porównajmy prostą funkcję bez i z curryowaniem:
| Cecha | Bez curryowania (tradycyjne wywołanie) | Z curryowaniem (za pomocą dekoratora) |
|---|---|---|
| Definicja funkcji | def add(x, y, z): return x + y + z | @curry |
| Wywołanie funkcji | add(1, 2, 3) | add(1)(2)(3) |
| Częściowe zastosowanie | Wymaga functools.partial lub nowej funkcji | Naturalnie wspiera stopniowe dostarczanie argumentów |
| Elastyczność | Mniej elastyczne w tworzeniu wyspecjalizowanych wersji | Pozwala na łatwe tworzenie nowych funkcji z predefiniowanymi argumentami |
Najczęściej zadawane pytania
Czy curryowanie to to samo co częściowe aplikowanie?
Nie, chociaż są ze sobą powiązane i często mylone. Curryowanie przekształca funkcję przyjmującą wiele argumentów w serię funkcji, z których każda przyjmuje dokładnie jeden argument. Częściowe aplikowanie funkcji (jak za pomocą functools.partial) tworzy nową funkcję poprzez „zamrożenie” pewnej liczby początkowych argumentów, ale wynikowa funkcja może nadal przyjmować wiele argumentów jednocześnie.
Do czego służy `functools.partial`?
functools.partial służy do tworzenia nowych funkcji z istniejących, poprzez ustalenie wartości dla jednego lub więcej początkowych argumentów. Jest to wygodne narzędzie do tworzenia wyspecjalizowanych wersji ogólnych funkcji bez konieczności definiowania nowych funkcji od podstaw.
Czy curryowanie jest specyficzne dla Pythona?
Nie, curryowanie to koncepcja wywodząca się z matematyki i logiki, szeroko stosowana w programowaniu funkcyjnym. Jest obecna w wielu językach programowania, szczególnie w tych, które silnie wspierają paradygmat funkcyjny, takich jak Haskell, Scala, JavaScript (ES6+), czy F#.
Jakie są główne zalety curryowania?
Główne zalety to poprawa modułowości i możliwości ponownego użycia kodu, ułatwienie kompozycji funkcji, lepsze zarządzanie stanem w niektórych scenariuszach asynchronicznych oraz redukcja redundancji kodu poprzez tworzenie bardziej specyficznych funkcji z ogólnych szablonów.
Podsumowanie
Curryowanie to potężna technika programowania funkcyjnego, która pozwala na dekompozycję funkcji wieloargumentowych na sekwencje funkcji jednoargumentowych. Chociaż początkowo może wydawać się abstrakcyjne, jego zastosowania w tworzeniu bardziej elastycznego, czytelnego i modułowego kodu są znaczące. Od tworzenia własnych dekoratorów, przez wykorzystanie wbudowanych narzędzi jak functools.partial, po bardziej zaawansowane implementacje obsługujące dowolną liczbę argumentów, curryowanie otwiera nowe możliwości w projektowaniu oprogramowania. Zrozumienie tej koncepcji może wzbogacić Twój zestaw narzędzi programistycznych i pozwolić na pisanie bardziej eleganckiego i efektywnego kodu w Pythonie.
Zainteresował Cię artykuł Curryowanie w Pythonie: Sekwencja Funkcji? Zajrzyj też do kategorii Kulinaria, znajdziesz tam więcej podobnych treści!
