Mateusz Mazurek – programista z pasją

Czyli o użyciu Pythona i kilku innych technologii do tworzenia świetnej jakości aplikacji w oparciu o stabilny proces dostarczania oprogramowania.

Programowanie Programowanie webowe

Partial w Pythonie – zastosowanie, przykłady

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.

……

Ale że cooo?!

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:
a w ramach prezentu otrzymasz całkowicie za darmo, dwa dokumenty PDF „6 (nie zawsze oczywistych) błędów popełnianych podczas nauki programowania” który jest jednym z efektów ponad siedmioletniej pracy i obserwacji rozwoju niejednego programisty oraz „Wstęp do testowania w Pythonie”, będący wprowadzeniem do biblioteki PyTest.
Jeśli to Cię interesuje to zapraszam również na swoje social media.

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.

Dzięki za wizytę,
Mateusz Mazurek

A może wolisz nowości na mail?

Subskrybuj
Powiadom o
guest

Witryna wykorzystuje Akismet, aby ograniczyć spam. Dowiedz się więcej jak przetwarzane są dane komentarzy.

3 komentarzy
Inline Feedbacks
View all comments

[…] równą 0 i nie ma potrzeby, by pojawiały się one w inicie, więc korzystając z funkcji partial zrobiłem alias do funkcji field, dostając w rezultacie funkcję without_init, której użyłem do […]

Mateusz Hyla

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