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:

Aby potwierdzić swoją subskrypcję, odbierz pocztę i kliknij w link potwierdzający:) jeśli maila nie ma to poczekaj chwile i/lub sprawdź folder spam/inne/oferty itp :)

Aby potwierdzić swoją subskrypcję, odbierz pocztę i kliknij w link potwierdzający:) jeśli maila nie ma to poczekaj chwile i/lub sprawdź folder spam/inne/oferty itp :)
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.

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.

Dzięki za wizytę,
Mateusz Mazurek
Mateusz M.

Pokaż komentarze

  • 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:)

  • 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.

Ostatnie wpisy

Podsumowanie: maj, czerwiec, lipiec i sierpień 2024

Oj daaawnoo mnie tu nie było. Ale wakacje to był czas dużej liczby intensywnych wyjazdów i tak naprawdę, dopiero jakoś… Read More

2 miesiące ago

Podsumowanie: kwiecień 2024

Cześć! Zapraszam na krótkie podsumowanie kwietnia. Wyjazd do Niemiec A dokładniej pod granicę z Francją. Chrześnica miała pierwszą komunię. Po… Read More

6 miesięcy ago

Podsumowanie: luty i marzec 2024

Ostatnio tygodnie były tak bardzo wypełnione, że nie udało mi się napisać nawet krótkiego podsumowanie. Więc dziś zbiorczo podsumuję luty… Read More

7 miesięcy ago

Podsumowanie: styczeń 2024

Zapraszam na krótkie podsumowanie miesiąca. Książki W styczniu przeczytałem "Homo Deus: Historia jutra". Książka łudząco podoba do wcześniejszej książki tego… Read More

9 miesięcy ago

Podsumowanie roku 2023

Cześć! Zapraszam na podsumowanie roku 2023. Książki Zacznijmy od książek. W tym roku cel 35 książek nie został osiągnięty. Niemniej… Read More

10 miesięcy ago

Podsumowanie: grudzień 2023

Zapraszam na krótkie podsumowanie miesiąca. Książki W grudniu skończyłem czytać Mein Kampf. Nudna książka. Ciekawsze fragmenty można by było streścić… Read More

10 miesięcy ago