Jak używać przeciążania metod w Pythonie?


169

Próbuję zaimplementować przeciążanie metod w Pythonie:

class A:
    def stackoverflow(self):    
        print 'first method'
    def stackoverflow(self, i):
        print 'second method', i

ob=A()
ob.stackoverflow(2)

ale wynik jest second method 2; podobnie:

class A:
    def stackoverflow(self):    
        print 'first method'
    def stackoverflow(self, i):
        print 'second method', i

ob=A()
ob.stackoverflow()

daje

Traceback (most recent call last):
  File "my.py", line 9, in <module>
    ob.stackoverflow()
TypeError: stackoverflow() takes exactly 2 arguments (1 given)

Jak to działa?


39
W Pythonie myśl o metodach jako o specjalnym zestawie „ atrybutów ” i może istnieć tylko jeden „ atrybut ” (a więc jedna metoda) danej nazwy obiektu. Ostatnia metoda zastępuje wszystkie poprzednie metody. W Javie metody nie są obywatelami pierwszej klasy (nie są „atrybutami obiektów”), ale są raczej wywoływane przez „wysyłanie wiadomości”, które są statycznie rozwiązywane w oparciu o najbliższy typ (w którym pojawia się przeciążenie ).



6
Dlaczego żadna z odpowiedzi na to pytanie nie została jeszcze zaakceptowana? Po prostu kliknij nieaktualny znacznik wyboru po lewej stronie ulubionej odpowiedzi ...
glglgl

Odpowiedzi:


150

Jest to przeciążenie metody, a nie przesłanianie metody . A w Pythonie robisz to wszystko w jednej funkcji:

class A:

    def stackoverflow(self, i='some_default_value'):    
        print 'only method'

ob=A()
ob.stackoverflow(2)
ob.stackoverflow()

Nie możesz mieć dwóch metod o tej samej nazwie w Pythonie - i nie musisz.

Zobacz sekcję Domyślne wartości argumentów samouczka języka Python. Zobacz „Najmniej zdziwienia” i Argument zmiennego domniemania, aby znaleźć typowy błąd, którego należy unikać.

Edycja : zobacz PEP 443, aby uzyskać informacje o nowych funkcjach ogólnych pojedynczego wysyłania w Pythonie 3.4.


119
i nie musisz tego robić - IMHO czasami przydałoby się przeładowanie metod, jak np. w C ++. Ok, nie jest to „potrzebne” w tym sensie, że nie da się tego zrobić przy użyciu innych konstrukcji - ale ułatwiłoby to niektóre rzeczy.
Andreas Florath

7
@AndreasFlorath Nie zgadzam się. Naucz się kochać pisanie typu kaczego i napisz każdą metodę, aby wykonywała tylko jedną rzecz i nie ma potrzeby jej przeciążania.
agf

4
+1 za
zmuszenie

43
Chciałbym się trochę nie zgodzić;) ... przeładowanie często czyni kod czystszym, ponieważ nie pakujesz metody ze zbyt dużą liczbą instrukcji if-else, aby obsłużyć różne przypadki. W pewnym sensie cała gama języków funkcjonalnych posługuje się podobną ideą, tj. Dopasowywaniem wzorców argumentów. Co oznacza, że ​​miałbyś mniejsze i czystsze metody ... zamiast gigantycznych, nieczytelnych metod.
sten

2
@ user1019129 Taki jest cel ogólnych funkcji jednorazowej wysyłki dodanych w Pythonie 3.4 i połączonych w mojej odpowiedzi.
agf

62

Możesz także użyć pythonlangutil :

from pythonlangutil.overload import Overload, signature

class A:
    @Overload
    @signature()
    def stackoverflow(self):    
        print 'first method'

    @stackoverflow.overload
    @signature("int")
    def stackoverflow(self, i):
        print 'second method', i

6
Myślę, że to jedyna ważna odpowiedź na to pytanie. Gdybym mógł, zagłosowałbym dwukrotnie za.
Michael

3
jest dobrze, ale nie działa na funkcjach surowych, tylko na metodach w klasie.
Legit Stack

1
@LegitStack Tę funkcjonalność można również dodać. To nie jest niemożliwe.
Ehsan Keshavarzian

3
@LegitStack Zaktualizowałem kod na GitHubie, teraz działa też z funkcjami.
Ehsan Keshavarzian

1
@PaulPrice Zgadza się. Zaktualizowałem swoją odpowiedź i usunąłem oficjalną sekcję wsparcia. Nadal możesz używać mojego kodu do usuwania przeciążeń. Teraz działa zarówno z metodami, jak i funkcjami. Pobierz kod z GitHub. Nie zaktualizowałem jeszcze PyPi.
Ehsan Keshavarzian

48

W Pythonie nie robisz rzeczy w ten sposób. Kiedy ludzie robią to w językach takich jak Java, zazwyczaj chcą mieć wartość domyślną (jeśli tego nie robią, zazwyczaj chcą metody o innej nazwie). Tak więc w Pythonie możesz mieć wartości domyślne .

class A(object):  # Remember the ``object`` bit when working in Python 2.x

    def stackoverflow(self, i=None):
        if i is None:
            print 'first form'
        else:
            print 'second form'

Jak widać, to można to wykorzystać do uruchomienia osobnego zachowanie, a nie tylko o wartości domyślnej.

>>> ob = A()
>>> ob.stackoverflow()
first form
>>> ob.stackoverflow(2)
second form

2
Przeważnie Nonejest przydatne, gdy chcesz zmienić domyślną wartość. Oddzielne zachowanie powinno znajdować się w osobnych funkcjach.
agf

@agf: Nonemoże być również przydatna jako prawdziwa wartość domyślna.
Chris Morgan

Tak, ale odnosiłem się do używania go jako wartości wartowniczej, czyli w jaki sposób używasz go w swojej odpowiedzi i jak sądzę, mój komentarz wyjaśnia to jasno.
agf

mówisz „ogólnie”? sugerujesz, że nie zawsze tak jest?
joel

24

Nie możesz, nigdy nie musisz i naprawdę nie chcesz.

W Pythonie wszystko jest obiektem. Klasy są rzeczami, więc są przedmiotami. Tak samo jak metody.

Istnieje obiekt o nazwie, Aktóry jest klasą. Posiada atrybut o nazwie stackoverflow. Może mieć tylko jeden taki atrybut.

Kiedy piszesz def stackoverflow(...): ..., dzieje się tak, że tworzysz obiekt, który jest metodą i przypisujesz go dostackoverflow atrybutu A. Jeśli napiszesz dwie definicje, druga zastąpi pierwszą, tak jak zawsze zachowuje się przypisanie.

Ponadto nie chcesz pisać kodu, który wykonuje dzikie czynności, do których czasami używa się przeciążenia. Nie tak działa język.

Zamiast próbować zdefiniować osobną funkcję dla każdego typu rzeczy, które możesz otrzymać (co nie ma sensu, ponieważ i tak nie określasz typów parametrów funkcji), przestań się martwić, czym rzeczy i zacznij myśleć o tym, co mogą zrobić .

Nie tylko nie możesz napisać oddzielnego, aby obsłużyć krotkę w porównaniu z listą, ale także nie chcesz lub nie musisz tego robić .

Wszystko, co musisz zrobić, to wykorzystać fakt, że oba są na przykład iterowalne (czyli możesz pisać for element in container:). (Fakt, że nie są one bezpośrednio powiązane dziedziczeniem, nie ma znaczenia).


6
TBH, byłbym bardziej ostrożny z „nigdy nie muszę”. To jest coś, co można oznaczyć na każdej funkcji dowolnego rzeczywistego świata i w całym języku programowania, a zatem nie jest to ważny argument. Kto potrzebuje generatorów? Kto potrzebuje zajęć? Języki programowania to po prostu cukier syntaktyczny do czegoś bardziej konkretnego.
Sebastian Mach

6
Całkowicie się nie zgadzam. Może się zdarzyć, że „nigdy nie musiałeś” lub „nigdy nie chciałeś”, ale jest wystarczająco dużo aplikacji, w których desperacko tego chcesz. Spróbuj na przykład pisząc program, który obsługuje zarówno Python i numpy tablice wdziękiem bez zaśmiecanie swój program z instancją w ...
Elmar Zander

1
Opierając się na odpowiedzi Masiego, powiedziałbym, że „nie możesz” jest teraz niepoprawne i przestarzałe. Opierając się na istnieniu @overloaddekoratora, powiedziałbym, że „naprawdę nie chcę” jest w najlepszym przypadku dyskusyjna. Od PEP-3124, „… jest to obecnie powszechny wzorzec dla kodu Pythona do sprawdzania typów odbieranych argumentów…„ oczywistym sposobem ”na to jest kontrola typu, ale jest to kruche i zamknięte dla rozszerzenie ... "Wygląda więc na to, że wystarczająco dużo ludzi tego chciało, że stało się częścią Pythona.
Mike S

@MikeS, standard @overloadjest przeznaczony tylko do pisania.
Narfanar

@Narfanar Nie wiem, jak Twoja odpowiedź odnosi się do mojego komentarza. Czy możesz wytłumaczyć?
Mike S

16

Podczas gdy @agf miał rację z odpowiedzią w przeszłości, teraz z PEP-3124 , otrzymaliśmy nasz cukier składniowy. Zobacz dokumentację dotyczącą pisania, aby uzyskać szczegółowe informacje na temat @overloaddekoratora, ale zwróć uwagę, że to tak naprawdę jest tylko cukier składniowy i IMHO to wszystko, o co ludzie się kłócili od tego czasu. Osobiście zgadzam się, że o wiele funkcji z różnych podpisów czyni go bardziej czytelnym następnie posiadające jedną funkcję z 20+ argumentów wszystko ustawione na wartość domyślną ( Noneprzez większość czasu), a następnie konieczności pobawić wokół używając nieskończone if, elif, elsełańcuchy, aby dowiedzieć się, co wywołujący w rzeczywistości chce, aby nasza funkcja działała z podanym zestawem argumentów. Było to dawno spóźnione po Python Zen

Piękne jest lepsze niż brzydkie.

i prawdopodobnie również

Proste jest lepsze niż złożone.

Prosto z oficjalnej dokumentacji Pythona, do której link znajduje się powyżej:

from typing import overload
@overload
def process(response: None) -> None:
    ...
@overload
def process(response: int) -> Tuple[int, str]:
    ...
@overload
def process(response: bytes) -> str:
    ...
def process(response):
    <actual implementation>

dokładnie to, czego szukałem, schludniej niż definiowanie własnego przeciążającego dekoratora
pcko1

2
btw: dla każdego, kto zastanawia się, dlaczego to nie działa, proponuję zajrzeć do dyskusji na stackoverflow.com/questions/52034771/… - @overloadfunkcje ed nie powinny mieć żadnej rzeczywistej implementacji. Nie jest to oczywiste z przykładu w dokumentacji Pythona.
masi

15

Piszę swoją odpowiedź w Pythonie 3.2.1.

def overload(*functions):
    return lambda *args, **kwargs: functions[len(args)](*args, **kwargs)

Jak to działa:

  1. overloadpobiera dowolną liczbę wywołań i przechowuje je w krotce functions, a następnie zwraca lambda.
  2. Lambda pobiera dowolną ilość argumentów, a następnie zwraca wynik wywołania funkcji zapisanej w functions[number_of_unnamed_args_passed]wywołanej funkcji z argumentami przekazanymi do lambdy.

Stosowanie:

class A:
    stackoverflow=overload(                    \
        None, \ 
        #there is always a self argument, so this should never get called
        lambda self: print('First method'),      \
        lambda self, i: print('Second method', i) \
    )

14

Myślę, że słowo, którego szukasz, to „przeciążenie”. W Pythonie nie ma przeciążenia metod. Możesz jednak użyć domyślnych argumentów w następujący sposób.

def stackoverflow(self, i=None):
    if i != None:     
        print 'second method', i
    else:
        print 'first method'

Kiedy przekażesz mu argument, będzie on postępował zgodnie z logiką pierwszego warunku i wykona pierwszą instrukcję print. Gdy nie przekażesz mu żadnych argumentów, przejdzie do elsewarunku i wykona drugą instrukcję print.


13

Piszę swoją odpowiedź w Pythonie 2.7:

W Pythonie przeciążanie metod nie jest możliwe; jeśli naprawdę chcesz uzyskać dostęp do tej samej funkcji z różnymi funkcjami, sugeruję, aby przejść do nadpisywania metody.

class Base(): # Base class
    '''def add(self,a,b):
        s=a+b
        print s'''

    def add(self,a,b,c):
        self.a=a
        self.b=b
        self.c=c

        sum =a+b+c
        print sum

class Derived(Base): # Derived class
    def add(self,a,b): # overriding method
        sum=a+b
        print sum



add_fun_1=Base() #instance creation for Base class
add_fun_2=Derived()#instance creation for Derived class

add_fun_1.add(4,2,5) # function with 3 arguments
add_fun_2.add(4,2)   # function with 2 arguments

9

W Pythonie przeciążenie nie jest pojęciem stosowanym. Jeśli jednak próbujesz utworzyć przypadek, w którym na przykład chcesz, aby jeden inicjator był wykonywany, jeśli przekazano argument typu fooi inny inicjator dla argumentu typu bar, ponieważ wszystko w Pythonie jest obsługiwane jako obiekt, możesz sprawdzić nazwę typu klasy przekazanego obiektu i na tej podstawie napisz obsługę warunkową.

class A:
   def __init__(self, arg)
      # Get the Argument's class type as a String
      argClass = arg.__class__.__name__

      if argClass == 'foo':
         print 'Arg is of type "foo"'
         ...
      elif argClass == 'bar':
         print 'Arg is of type "bar"'
         ...
      else
         print 'Arg is of a different type'
         ...

W razie potrzeby tę koncepcję można zastosować do wielu różnych scenariuszy za pomocą różnych metod.


7

W Pythonie zrobiłbyś to z domyślnym argumentem.

class A:

    def stackoverflow(self, i=None):    
        if i == None:
            print 'first method'
        else:
            print 'second method',i

5

Właśnie znalazłem ten https://github.com/bintoro/overloading.py dla każdego, kto może być zainteresowany.

Z pliku readme połączonego repozytorium:

overloading to moduł, który zapewnia wysyłanie funkcji na podstawie typów i liczby argumentów czasu wykonywania.

W przypadku wywołania przeciążonej funkcji dyspozytor porównuje podane argumenty z dostępnymi sygnaturami funkcji i wywołuje implementację, która zapewnia najdokładniejsze dopasowanie.

cechy

Walidacja funkcji po rejestracji i szczegółowe zasady rozwiązywania gwarantują unikalny, dobrze zdefiniowany wynik w czasie wykonywania. Implementuje buforowanie rozdzielczości funkcji dla doskonałej wydajności. Obsługuje parametry opcjonalne (wartości domyślne) w sygnaturach funkcji. Ocenia argumenty pozycyjne i słowa kluczowe podczas rozwiązywania najlepszego dopasowania. Obsługuje funkcje rezerwowe i wykonywanie udostępnionego kodu. Obsługuje polimorfizm argumentów. Obsługuje klasy i dziedziczenie, w tym metody klas i metody statyczne.


3

Python nie obsługuje przeciążania metod, takich jak Java czy C ++. Możemy przeciążać metody, ale możemy używać tylko najnowszej zdefiniowanej metody.

# First sum method.
# Takes two argument and print their sum
def sum(a, b):
    s = a + b
    print(s)

# Second sum method
# Takes three argument and print their sum
def sum(a, b, c):
    s = a + b + c
    print(s)

# Uncommenting the below line shows an error    
# sum(4, 5)

# This line will call the second sum method
sum(4, 5, 5)

Musimy podać opcjonalne argumenty lub * args, aby zapewnić inną liczbę argumentów podczas wywoływania.

Dzięki uprzejmości https://www.geeksforgeeks.org/python-method-overloading/


To nie jest przeciążenie. To się nazywa nadpisywanie. Ta ostatnia jest obsługiwana przez Pythona. Pierwszy można zrealizować za pomocą dekoratorów.
Paebbels

2

Python 3.x zawiera standardową bibliotekę typowania, która pozwala na przeciążanie metody przy użyciu dekoratora @overload. Niestety, ma to na celu uczynienie kodu bardziej czytelnym, ponieważ po metodzie dekorowanej @overload musi następować metoda bez dekoracji, która obsługuje różne argumenty. Więcej można znaleźć tutaj , ale na przykład:

from typing import overload
from typing import Any, Optional
class A(object):
    @overload
    def stackoverflow(self) -> None:    
        print('first method')
    @overload
    def stackoverflow(self, i: Any) -> None:
        print('second method', i)
    def stackoverflow(self, i: Optional[Any] = None) -> None:
        if not i:
            print('first method')
        else:
            print('second method', i)

ob=A()
ob.stackoverflow(2)

1
„The” na końcu twojej odpowiedzi sprawia, że ​​myślę, że nie skończyłeś pisać swojej odpowiedzi. Proszę edytować swoją odpowiedź, aby go zakończyć.
Artjom B.,

0

W pliku MathMethod.py

from multipledispatch import dispatch
@dispatch(int,int)
def Add(a,b):
   return a+b 
@dispatch(int,int,int)  
def Add(a,b,c):
   return a+b+c 
@dispatch(int,int,int,int)    
def Add(a,b,c,d):
   return a+b+c+d

W pliku Main.py

import MathMethod as MM 
print(MM.Add(200,1000,1000,200))

Możemy przeciążyć metodę używając multipledispatch


Wymaga to użycia pakietu multipledispatch ( pypi.org/project/multipledispatch ), który nie jest częścią rdzenia Pythona.
Antony

0

Python dodał dekorator @overload z PEP-3124 aby zapewnić cukier syntaktyczny do przeciążania poprzez kontrolę typu - zamiast po prostu pracować z nadpisywaniem.

Przykład kodu w przypadku przeciążania przez @overload z PEP-3124

from overloading import overload
from collections import Iterable

def flatten(ob):
    """Flatten an object to its component iterables"""
    yield ob

@overload
def flatten(ob: Iterable):
    for o in ob:
        for ob in flatten(o):
            yield ob

@overload
def flatten(ob: basestring):
    yield ob

jest przekształcany przez dekorator @ overload na:

def flatten(ob):
    if isinstance(ob, basestring) or not isinstance(ob, Iterable):
        yield ob
    else:
        for o in ob:
            for ob in flatten(o):
                yield ob
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.