Cześć, po małym skoku w bok – wracamy do programowania.
Poopowiadam dziś trochę o ciekawej funkcji, którą możemy zaleźć w bibliotece standardowej Python’a, a mianowicie o funkcji partial.
Zwraca ona obiekt partial, który zachowuje się jak funkcja przekazana w pierwszym argumencie omawianej metody, ale z już wypełnionymi argumentami . Brzmi nudno? Poczekajcie dalej ;)
Jak działa funkcja partial „od kuchni” ?
Tak mniej więcej mogłaby wyglądać implementacja funkcji partial
1 2 3 4 5 6 7 8 9 | def partial(func, *args, **keywords): def newfunc(*fargs, **fkeywords): newkeywords = keywords.copy() newkeywords.update(fkeywords) return func(*args, *fargs, **newkeywords) newfunc.func = func newfunc.args = args newfunc.keywords = keywords return newfunc |
Jak widać wyżej działanie funkcji partial opiera się o stworzenie nowej funkcji, która będzie miała zapamiętane te argumenty (pozycyjne i jak nazwane), które były podane jako kolejne, po funkcji, która ma być dekorowana. Innymi słowy – powyższy kod pokazuje sposób na zapamiętanie pewnej porcji argumentów funkcji, tak by zredukować ich ilość przy jej wywołaniu, nadpisując tę „pewną porcję argumentów”, argumentami podanymi przy tworzeniu udekorowanej funkcji.
……
Zobaczmy pierwszy, prosty przykład
Załóżmy, że mamy funkcję:
1 2 | def power(x: int, y: int): return x**y |
Prosty kawałek kodu, który podnosi liczbę x do potęgi y. Nie potrzeba ogromnej wiedzy matematycznej, by zauważyć, że takie wywołania:
1 2 3 | print(power(2, 2)) print(power(2, 3)) print(power(2, 4)) |
wyprodukują nam liczby:
4
8
16
szok i niedowierzanie, prawda? :)
Teraz chcemy stworzyć funkcję, która będzie miała jeden argument, który zawsze będzie podnosić do kwadratu. Możemy oczywiście zrobić to tak:
1 2 3 4 5 | def power(x: int, y: int): return x**y def square(x: int): return power(x, 2) |
i wtedy nasza funkcja square będzie robić dokładnie to co napisałem, a wiec wywołania:
1 2 3 | print(square(2)) print(square(3)) print(square(4)) |
wyprodukują:
4
9
16
Ale, ale, ale… Znając funkcję partial możemy zrobić to ładniej, np tak:
1 2 3 4 5 6 7 8 9 10 11 12 | from functools import partial def power(x: int, y: int): return x**y square = partial(power, y=2) print(square(2)) print(square(3)) print(square(4)) |
Funkcja partial w tym przypadku weźmie funkcję power, zapisze argument y oraz zwróci funkcję, która automatycznie przy wywołaniu przekaże zapisany argument jako argument dekorowanej funkcji. I ten kod również wyprodukuje ten sam wynik:
4
9
16
Drugi prosty, ale już sprytny przykład
Czy wiesz, że Python’owa funkcja int konwertująca argument na typ int posiada drugi argument, który mówi jej jak ma traktować swój pierwszy argument? Dokładniej, mówi o tym, jaka jest podstawa pierwszego argumentu, domyślna wartość to oczywiście 10.
Mając tę wiedzę, plus wiedzę o funkcji partial, możemy bardzo łatwo napisać funkcję, która konwertuje zapis dwójkowy na zapis dziesiętny:
1 2 | bin_to_dec = partial(int, base=2) print(bin_to_dec('111')) |
Tutaj nadpisujemy drugi argument funkcji int wartością dwa i tyle! Rezultat tego kawałka kodu to oczywiście… 7!
Bardziej życiowy przykład
Wyobraźmy sobie, że mamy system, który reaguje na jakieś zdarzenie. Trochę na zasadzie komunikacji pomiędzy komponentami, coś jak wzorzec obserwatora – gdy jeden obiekt powiadamia drugi.
Czekaj, stop!
Podoba Ci się to co tworzę? Jeśli tak to zapraszam Cię do zapisania się na newsletter:Jeśli to Cię interesuje to zapraszam również na swoje social media.
Jak i do ewentualnego postawienia mi kawy :)
Załóżmy, że mamy taki kawałek kodu:
1 2 3 4 5 6 7 8 9 10 11 12 | from datetime import datetime class Event(object): def __init__(self): self._listeners = [] def add_listener(self, callback): print('Listener added') self._listeners.append(callback) def fire(self): for f in self._listeners: f(self, datetime.now()) |
Czyli klasa, która jest Zdarzeniem i pozwala zasubskrybować się na moment wywołania tego zdarzenia. Kiedy ten moment nadejdzie, odpalana jest funkcje fire i zasubskrybowani obserwatorzy są o tym powiadamiani.
Czyli – łatwe, proste i przyjemne!
Zobaczmy kolejny przykład użycia:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | from datetime import datetime class Event(object): def __init__(self): self._listeners = [] def add_listener(self, callback): print('Listener added') self._listeners.append(callback) def fire(self): for f in self._listeners: f(self, datetime.now()) def callback(log, event, datetime): log('Event ' + str(event) + ' raised at ' + str(datetime)) e = Event() e.add_listener(callback) e.fire() |
Tym razem próba wykonania zakończy się błędem:
f(self, datetime.now())
TypeError: callback() missing 1 required positional argument: 'datetime'
Powód jest jasny – jako callback podaliśmy funkcję przyjmującą 3 argumenty a Event obsługuje callbacki mające 2 argumenty. Python informuje nas o brakującym argumencie pozycyjnym.
Zakładając, że klasa Event jest klasą pochodzącą z bibliotek zależnych naszego projektu i nie możemy jej zmienić, to zaczyna się robić trochę słabo.
Na szczęście, dzięki funkcji partial, możemy rozwiązać problem całkiem prosto, a dokładnie:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | from functools import partial from datetime import datetime class Event(object): def __init__(self): self._listeners = [] def add_listener(self, callback): print('Listener added') self._listeners.append(callback) def fire(self): for f in self._listeners: f(self, datetime.now()) def callback(log, event, datetime): log('Event ' + str(event) + ' raised at ' + str(datetime)) log = print callback = partial(callback, log) e = Event() e.add_listener(callback) e.fire() |
I taki kawałek kodu już się uruchomi poprawnie:
Listener added
Event <main.Event object at 0x7fc8a8bb3ba8> raised at 2019-02-08 17:53:36.533804
Zmierzając do brzegu
Widać, że funkcja partial może szybko i łatwo sprawić, że nasze życie będzie prostsze. Warto o niej pamiętać – w elegancki sposób rozwiązuje niektóre problemy.
Mateusz Mazurek
Bardzo fajny artykuł. Fajnie to wytłumaczyłeś na przykładzie potęgowania. Lubie jezyk Python ale to jeszcze nie moj poziom.
Widze ze czytasz duzo fantastyki. Mnie interesuje oprocz informatyki i matmy rzecz jasna jeszcze psychologia. Tez czytam dosc sporo ksiazek. O ksiazce Dale-a Carnagie „How to win friends and influence people” napisalem na swoim blogu.
http://mstem.net/must-read-book-1/
Chetnie dodam cie Mateusz do znajomych na Linkedin.com 👍🙂.
Pozdrawiam
Potwierdziłem naszą znajomość na LinkedIn:) A książkę o której wspomniałeś czytałem (polskie tłumaczenie) i polecam równie mocno jak Ty:)
[…] Partial w Pythonie – zastosowanie, przykłady […]
Dzięki za artykuł. Te bardziej rozbudowane przykłady niestety dla mnie zbyt skomplikowane jak na początek ;) ale doceniam, że zacząłeś od czegoś prostszego dla przedstawienia idei. pozdrawiam.
Cieszę się, że artykuł chociaż trochę pomógł:)