Historia pytania:
W Pythonie wszystko jest obiektem, a każda funkcja również jest obiektem. Wprowadzono mechanizm, który pozwala instancji klasy zachowywać się jak funkcja. W tym celu wprowadzono specjalną metodę magiczną call, która pozwala na wywoływanie obiektów.
Problem:
Czasami wymagane jest przekazanie obiektu, który może wykonywać działania podczas wywołania, np. funkcji ze stanem (funkcje stanowe), zamknięcia, obiekty obsługi zdarzeń itp. Opracowanie architektury wymaga zrozumienia, jak prawidłowo zaimplementować taką funkcjonalność.
Rozwiązanie:
Implementując metodę call w klasie, można uczynić jej instancję wywoływaną „jak funkcję”. Pozwala to połączyć możliwości klas (inkapsulacja stanu, dziedziczenie, metody) oraz funkcji (wywoływalność). Takie podejście jest stosowane do tworzenia obiektów komend, złożonych handlerów, wrapperów itd.
Przykład kodu:
class Adder: def __init__(self, x): self.x = x def __call__(self, y): return self.x + y add5 = Adder(5) print(add5(10)) # Zwróci 15
Kluczowe cechy:
Czy metoda call dziedziczy atrybuty zwykłej funkcji — na przykład name i doc?
Nie, obiekt z metodą call nie będzie miał atrybutu name (lub weźmie od klasy). Metadane funkcji nie są zachowywane.
Czy obiekt z zaimplementowanym call jest prawdziwą funkcją?
Nie, to instancja klasy, a nie funkcja. Tylko implementuje „wywoływalne” zachowanie. Na przykład, porównanie go z funkcją za pomocą isinstance(obj, types.FunctionType) zwróci False.
Czy można zastosować do obiektu z call dekorator przeznaczony dla funkcji?
Zazwyczaj takie dekoratory oczekują właśnie funkcji, a nie obiektu (na przykład, functools.lru_cache). Użycie może prowadzić do błędów lub w ogóle nie zadziała.
Zalety:
Negatywny przypadek: W projekcie zaimplementowano loggera za pomocą klasy z call, aby przechowywać ustawienia (poziom, nazwa pliku). Jednak zapomniano, że funkcje handlerów sygnałów oczekują właśnie funkcji, i otrzymywano błędy przy rejestracji handlera (object is not a function).
Zalety: elastyczna konfiguracja loggera. Wady: niezgodność z oczekiwanymi interfejsami.
Pozytywny przypadek: W innym projekcie użyto klasy z call do tworzenia złożonych funkcji-dekoratorów, przechowując parametry, co uprościło testowanie.
Zalety: rozszerzalność, wygoda. Wady: więcej kodu w porównaniu do funkcji lub lambda.