Zrozumienie przeciążania funkcji w Pythonie

Przeciążanie funkcji to funkcja dostępna w niektórych językach programowania, która umożliwia definiowanie odmian tej samej funkcji. Każdy wariant ma tę samą nazwę, ale różne implementacje i unikalne sygnatury funkcji.

Ta technika umożliwia wykonywanie różnych operacji w zależności od typu i liczby argumentów przekazanych do funkcji.

W przeciwieństwie do języków takich jak C++ i Java, Python domyślnie nie obsługuje przeciążania funkcji, ale istnieją sposoby na osiągnięcie podobnej funkcjonalności.

Jak Python radzi sobie z przeciążeniem funkcji?

W Pythonie możesz definiować tę samą funkcję więcej niż raz z różnymi parametrami, typami danych lub obydwoma w każdej definicji. Jednak Python rozpozna tylko ostatnią definicję funkcji, gdy ją wywołasz. Oto przykład:

 def arithmetics(a, b):
    return a - b

def arithmetics(a, b, c, d):
    return a + b - c * d

print(arithmetics(1, 2, 3, 5))
print(arithmetics(1, 2))

Języki obiektowe, takie jak Java, często obsługują przeciążanie funkcji i metod. Metoda to po prostu funkcja zdefiniowana wewnątrz klasy.

W powyższym kodzie Python rozpozna drugą definicję funkcji arithmetics() dopiero wtedy, gdy spróbujesz ją wywołać w swoim projekcie. Jeśli spróbujesz wywołać funkcję z dwoma argumentami, jak zdefiniowano na początku, pojawi się błąd „brak wymaganych argumentów pozycyjnych”.

Wywołanie funkcji z czterema argumentami nie spowoduje błędu. Oznacza to, że Python nadpisał funkcję najnowszą instancją. To nie jest zachowanie związane z przeciążeniem, więc musisz sobie z tym poradzić.

Tak więc Python domyślnie nie obsługuje przeciążania funkcji, ale istnieje kilka sztuczek, których możesz użyć do symulacji jego zachowania w swoich programach.

Metoda 1: Używanie parametrów opcjonalnych lub argumentów domyślnych

Przeciążenie można uzyskać, definiując funkcję z argumentami domyślnymi. Oto przykład:

 def arithmetics(a, b=0, c=0):
    """
    Arguments:
    a: The first number.
    b: The second number (optional).
    c: The third number (optional).
    """
    return a - b + c

Ta funkcja ma trzy parametry, ale dwa z nich mają wartości domyślne. Oznacza to, że możesz go wywołać z jednym do trzech argumentów:

 print(arithmetics(1)) 
print(arithmetics(2, 5))
print(arithmetics(10, 3, 4))

Chociaż takie podejście pozwala wywołać funkcję na kilka różnych sposobów, na dłuższą metę nie jest zbyt skuteczne. Oto niektóre z jego ograniczeń:

  • Można przekazywać tylko argumenty będące liczbami całkowitymi lub zmiennoprzecinkowymi.
  • Nie ma znaczących zmian w zachowaniu funkcji. Na przykład nie można zmienić jego zachowania, aby obliczyć obszar kształtu, a nawet wydrukować Hello World.

Metoda 2: Używanie zmiennych argumentów

Aby używać zmiennych argumentów do przeciążania funkcji w Pythonie, podczas definiowania funkcji należy uwzględnić parametr args. Parametr args umożliwia przekazanie wielu argumentów pozycyjnych podczas wywoływania funkcji. Oto przykład:

 def arithmetics(a, *args):
    """
    Arguments:
    a: The first number.
    *args: A variable number of arguments (optional).
    """
    args_sum = 0

    for num in args:
        args_sum *= num

    return a - args_sum

print(arithmetics(1))
print(arithmetics(2, 5))
print(arithmetics(10, 3, 4, 2, 4, 6))

Powyższa funkcja przyjmuje dwa argumenty: obowiązkowy argument zwany a oraz argument args, który pozwala na wprowadzenie dowolnej liczby argumentów.

Chociaż może przyjmować wiele argumentów, powyższa funkcja może wykonywać operację mnożenia tylko na argumentach zmiennych, tj. argumentach reprezentowanych przez słowo kluczowe args.

Jeśli chcesz wykonać wiele operacji, musisz wprowadzić do swojego kodu instrukcje warunkowe, a to może szybko się skomplikować.

Metoda 3: Korzystanie z dekoratora wielu wysyłek

Dekorator wielokrotnego wysyłania to biblioteka Pythona, która umożliwia definiowanie wielu implementacji lub instancji pojedynczej funkcji w oparciu o typ jej argumentów. Oznacza to, że możesz zdefiniować tę samą funkcję z różnymi typami danych i całkowicie zmienić jej zachowanie.

Aby użyć dekoratora wielu wysyłek, wykonaj następujące kroki:

  • Zainstaluj multipledispath w swoim środowisku Python:
     pip install multipledispatch
  • Udekoruj swoje funkcje za pomocą dekoratora @dispatch. Dekorator @dispatch to dekorator Pythona, który pozwala na implementację wielu wysyłek. Automatycznie wywoła odpowiednią funkcję na podstawie przekazanych jej argumentów. Możesz użyć dekoratora @dispatch, postępując zgodnie z tym wzorcem:
     from multipledispatch import dispatch

    @dispatch(data type1, data type2, data typeX)
    def your_function(a, b, c, x):
        pass
        

  • Oto przykład użycia dekoratora wielokrotnego wysyłania do przeciążania funkcji w Pythonie:

     from multipledispatch import dispatch

    @dispatch(int, int)
    def add(a, b):
        """
        Arguments:
        a: Any integer.
        b: Any integer.
        """
        return a + b

    @dispatch(int, list)
    def add(a, b):
        """
        Arguments:
        a: Any integer.
        b: Any Python list.
        """
        b.append(a)
        return b


    print(add(1, 2))


    print(add(1, [2, 3, 4, 5, 'w', 'done']))

    Powyższy fragment kodu definiuje dwie instancje funkcji add(). Pierwsza instancja przyjmuje jako argumenty dwie liczby całkowite i zwraca ich sumę.

    Tymczasem druga wersja tej funkcji przyjmuje liczbę całkowitą i listę. Dołącza liczbę całkowitą do listy i zwraca nową listę.

    Takie podejście do przeciążania funkcji w Pythonie zapewnia dużą elastyczność, szczególnie jeśli chcesz zmienić zachowanie swojej metody. Więcej dowiesz się z wielokrotna dokumentacja wysyłkowa.

    Najlepsze podejście do przeciążania funkcji w Pythonie

    Podejście do przeciążania funkcji powinno zależeć od tego, co próbujesz osiągnąć. Jeśli możesz wykonać zadanie, używając argumentów domyślnych lub zmiennych, wówczas dekorator wielokrotnego wysyłania może być przesadą. Jednak dekorator wielu wysyłek jest zwykle najlepszą opcją ze względu na jego wydajność i dokładność.

    Ten dekorator zapewnia przejrzysty i elastyczny sposób implementowania przeciążania funkcji w Pythonie. Umożliwia zdefiniowanie wielu implementacji pojedynczej funkcji w oparciu o typ jej argumentów.

    Dzięki takiemu podejściu można tworzyć elastyczne funkcje, które akceptują różne typy parametrów bez potrzeby stosowania skomplikowanych instrukcji warunkowych.