Pytanie
Co to jest callable object i czym on się różni od funkcji ?
Doprecyzujmy pytanie
Nie chodzi tu o różnice składniowe. Raczej interesuje nas rodzaj problemów jakie można przez te dwa podejścia rozwiązać.
Szybkie przypomnienie
Zwykła funkcja może wyglądać na przykład tak:
def nic_nie_rob(): print('serio nic nie robie') nic_nie_rob()
Output:
serio nic nie robie
Callable object (obiekt funkcyjny)
Jest to obiekt, do którego możesz odwoływać się jak do zwyczajnej funkcji. Oto jak wyglądałaby klasa, którą moglibyśmy zastąpić naszą funkcję:
class NicNieRob: def __call__(self): print('serio nic nie robie') nic_nie_rob = NicNieRob() nic_nie_rob()
Output:
serio nic nie robie
O co tu chodzi ?
Ok jeśli jednym i drugim sposobem osiągnęliśmy ten sam efekt to po co to wszytko. Odpowiedzi jest wiele. Przede wszystkim funkcje są bezstanowe. Zatem nie możemy zapisywać w nich informacji. Kiedy funkcja uruchamia się otrzymuje parametry i zwraca wartość. Każde kolejne uruchomienie funkcji robi dokładnie to samo. Funkcja nie pamięta nic ze swoich wcześniejszych uruchomień. Można powiedzieć, że funkcje mają bardzo krótką pamięć :). Inaczej wygląda sprawa z obiektami. Obiekty mogą mieć swój zapisany stan. Zatem ich pamięć może sięgać dalej niż od momentu wywołania do zwrócenia wartości.
Wykorzystajmy tą wiedzę
Jak już mówiliśmy callable object można zawołać jak funkcję. Potrafią one także zapamiętać swój stan. Napiszmy zatem funkcję, która będzie zliczać ilość swoich wywołań:
class NicNieRob: def __init__(self): self.licznik_wywolan = 0 def __call__(self): self.licznik_wywolan += 1 print('serio nic nie robie') nic_nie_rob = NicNieRob() nic_nie_rob() nic_nie_rob() nic_nie_rob() print(nic_nie_rob.licznik_wywolan)
Output:
serio nic nie robie serio nic nie robie serio nic nie robie 3
Udało się. Mamy funkcję (a dokładniej obiekt funkcyjny) robiącą dokładnie to samo co wcześniej czyli nic :). Dodatkowo każde wywołanie powoduje zwiększenie wskaźnika o jeden co w umożliwia zliczanie wywołań.
Czy to jedyna opcja ?
Czy to znaczy, że tylko przy użyciu callable object można osiągnąć taki efekt. Oczywiście, że nie.
Mamy jeszcze do dyspozycji funkcje wewnętrzne. Oto jak osiągnąć tą samą funkcjonalność bez użycia callable object:
def nic_nie_rob(): licznik_wywolan = 0 def funkcja_wewnetrzna(): nonlocal licznik_wywolan licznik_wywolan += 1 print('serio nic nie robie') funkcja_wewnetrzna.licznik_wywolan = licznik_wywolan return funkcja_wewnetrzna nic_nie_rob = nic_nie_rob() nic_nie_rob() nic_nie_rob() nic_nie_rob() print(nic_nie_rob.licznik_wywolan)
Output:
serio nic nie robie serio nic nie robie serio nic nie robie 3
W zasadzie to jesteśmy już o krok od dekoratorów, ale o tym już w kolejnym zadaniu :).