Как использовать перегрузку методов в Python?

169

Я пытаюсь реализовать перегрузку метода в Python:

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

ob=A()
ob.stackoverflow(2)

но выход есть second method 2; так же:

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

ob=A()
ob.stackoverflow()

дает

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

Как мне сделать эту работу?

user1335578
источник
39
В Python методы считаются особым набором « атрибутов », и для объекта может быть только один « атрибут » (и, следовательно, один метод) с заданным именем. Последний метод перезаписывает все предыдущие методы. В Java методы не являются первоклассными гражданами (они не являются «атрибутами объектов»), а скорее вызываются «отправкой сообщений», которые статически разрешаются на основе ближайшего типа (именно здесь возникает перегрузка ).
6
Почему ни один из ответов на этот вопрос еще не принят? Просто нажмите на галочку слева от вашего любимого ответа ...
glglgl
2
возможный дубликат перегруженных функций в python?
Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功

Ответы:

150

Это перегрузка метода, а не переопределение метода . А в Python вы делаете все это в одной функции:

class A:

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

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

Вы не можете иметь два метода с одинаковым именем в Python - и вам это не нужно.

Смотрите раздел Значения аргумента по умолчанию в руководстве по Python. См. «Наименьшее изумление» и изменяемый аргумент по умолчанию, чтобы избежать распространенной ошибки.

Редактировать : См. PEP 443 для получения информации о новых универсальных функциях единой диспетчеризации в Python 3.4.

AGF
источник
119
и вам не нужно - ИМХО когда-нибудь было бы очень удобно иметь перегрузку методов, как, например, в C ++. Хорошо, это не «необходимо» в том смысле, что это не может быть сделано с помощью других конструкций - но это сделает некоторые вещи проще и проще.
Андреас Флорат
7
@ AndreasFlorath Я не согласен. Научитесь любить печатать на уток и писать каждый метод, чтобы он делал только одно, и нет необходимости перегружать методы.
AGF
4
+1 за то, что заставил меня прочитать о «распространенной ошибке, которую нужно избегать» до того, как меня поймали
mdup
43
Я хотел бы немного не согласиться;) ... перегрузки часто делают код чище, потому что вы не упаковываете метод слишком большим количеством операторов if-else для обработки разных случаев. В некотором смысле вся гамма функциональных языков использует сходную идею, то есть сопоставление аргументов с образцами. Это означает, что у вас были бы более мелкие, более чистые методы, а не гигантские нечитаемые.
Стен
2
@ user1019129 Это цель универсальных функций единой отправки, добавленных в Python 3.4 и связанных в моем ответе.
августа
62

Вы также можете использовать 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
Я думаю, что это единственный действительный ответ на вопрос. Я бы удвоил голос, если бы мог.
Майкл
3
это хорошо, но он не работает с необработанными функциями, а только с методами внутри класса.
Legit Stack
1
@LegitStack Эта функциональность также может быть добавлена. Это не невозможно
Эхсан Кешаварзян
3
@LegitStack Я обновил код на GitHub, теперь он работает и с функциями.
Эхсан Кешаварзян
1
@PaulPrice Это верно. Я обновил свой ответ и удалил официальный раздел поддержки. Вы все еще можете использовать мой код для отправки перегрузок. Теперь он работает как с методами, так и с функциями. Возьмите код с GitHub. Я еще не обновил PyPi.
Эхсан Кешаварзян
48

В Python вы так не делаете. Когда люди делают это на таких языках, как Java, они обычно хотят получить значение по умолчанию (если они этого не делают, они обычно хотят метод с другим именем). Таким образом, в Python вы можете иметь значения по умолчанию .

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'

Как видите, вы можете использовать это для запуска отдельного поведения, а не просто для использования значения по умолчанию.

>>> ob = A()
>>> ob.stackoverflow()
first form
>>> ob.stackoverflow(2)
second form
Крис Морган
источник
2
Чаще всего Noneполезно, когда вы хотите изменить значение по умолчанию. Раздельное поведение должно быть в отдельных функциях.
2012 г.
@agf: Noneтакже может быть полезным в качестве подлинного значения по умолчанию.
Крис Морган
Да, но я имел в виду использование его в качестве дозорного значения, как вы используете его в своем ответе, и, как я думаю, мой комментарий проясняет.
2012 г.
ты говоришь "вообще"? ты намекаешь, что это не всегда так?
Joel
24

Вы не можете, никогда не должны и не хотите этого.

В Python все является объектом. Классы - это вещи, поэтому они являются объектами. Как и методы.

Существует объект с именем, Aкоторый является классом. У него есть атрибут с именем stackoverflow. У него может быть только один такой атрибут.

Когда вы пишете def stackoverflow(...): ..., происходит то, что вы создаете объект, который является методом, и назначаете его stackoverflowатрибуту A. Если вы пишете два определения, второе заменяет первое, так же, как присваивание всегда ведет себя.

Кроме того, вы не хотите писать код, который делает самые разные вещи, для которых иногда используется перегрузка. Это не так, как работает язык.

Вместо того, чтобы пытаться определить отдельную функцию для каждого типа вещей, которые вам могут дать (что в любом случае не имеет смысла, поскольку вы не указываете типы для параметров функции), перестаньте беспокоиться о том, что это такое , и начните думать о том, что они могут сделать .

Вы не только не можете написать отдельный для обработки кортеж против списка, но и не хотите или не должны .

Все, что вы делаете, это пользуетесь тем фактом, что они оба, например, итеративны (то есть вы можете писать for element in container:). (Тот факт, что они не имеют прямого отношения к наследованию, не имеет значения.)

Карл Кнехтель
источник
6
TBH, я был бы более осторожен с "никогда не надо". Это то, что можно пометить на каждой функции любого реального мира и создать полноценный язык программирования, и, следовательно, это недопустимый аргумент. Кому нужны генераторы? Кому нужны занятия? Языки программирования являются просто синтаксическим сахаром к чему-то более конкретному.
Себастьян Мах
6
Совершенно не согласен. Возможно, вам «никогда не нужно» или «никогда не хотелось», но есть достаточно приложений, в которых вы отчаянно хотите. Например, попробуйте написать программу, которая изящно обрабатывает массивы как Python, так и numpy, не засоряя вашу программу экземплярами instanceof ...
Elmar Zander
1
Основываясь на ответе Маси, я бы сказал, что «ты не можешь» теперь неверно и устарело. Основываясь на существовании @overloadдекоратора, я бы сказал , что « на самом деле не хотят» является спорным, в лучшем случае . Начиная с PEP-3124: «... в настоящее время Python-код является общим анти-паттерном для проверки типов полученных аргументов ...« очевидный способ »сделать это - проверка типа, но это хрупко и закрыто для расширение ... "Так что, кажется, достаточно людей хотели, чтобы он стал частью Python.
Майк С
@MikeS, стандарт @overloadтолько для набора текста.
Нарфанар
@ Нарфанар Я не знаю, как ваш ответ относится к моему комментарию. Могли бы вы объяснить?
Майк С.
16

Хотя @agf был прав с ответом в прошлом, теперь с PEP-3124 мы получили наш синтаксический сахар. См. Печатную документацию для получения подробной информации о @overloadдекораторе, но обратите внимание, что это на самом деле просто синтаксический сахар, и ИМХО, это все, о чем люди спорят с тех пор. Лично я согласен , что наличие нескольких функций с разными подписями делает его более удобным для чтения , то имея одну функции с аргументами 20+ всех заданного значения по умолчанию ( Noneбольшую часть времени) , а затем необходимости возиться с помощью бесконечных if, elif, elseцепей , чтобы выяснить , какие вызывающая сторона на самом деле хочет, чтобы наша функция работала с предоставленным набором аргументов. Это было давно пора после Python Zen

Красиво лучше, чем безобразно.

и, возможно, также

Простое лучше, чем сложное.

Прямо из официальной документации по Python, указанной выше:

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>
мази
источник
именно то, что я искал, аккуратнее, чем определение собственного декоратора перегрузки
pcko1
2
Кстати: для тех, кто интересуется, почему это не работает, я бы посоветовал взглянуть на обсуждение в stackoverflow.com/questions/52034771/… - @overloadфункции ed не должны иметь какой-либо реальной реализации. Это не очевидно из примера в документации по Python.
Маси
15

Я пишу свой ответ на Python 3.2.1.

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

Как это устроено:

  1. overloadпринимает любое количество вызываемых элементов и сохраняет их в кортеже functions, а затем возвращает лямбду.
  2. Лямбда принимает любое количество аргументов, а затем возвращает результат вызова функции, хранящейся в functions[number_of_unnamed_args_passed]вызываемой, с аргументами, передаваемыми в лямбду.

Использование:

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) \
    )
GingerPlusPlus
источник
14

Я думаю, что слово, которое вы ищете, это «перегрузка». В питоне нет перегрузки методов. Однако вы можете использовать аргументы по умолчанию следующим образом.

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

Когда вы передаете ему аргумент, он будет следовать логике первого условия и выполнять первый оператор печати. Когда вы передадите ему аргументы без аргументов, он перейдет в elseусловие и выполнит второй оператор print.

mayhewr
источник
13

Я пишу свой ответ на Python 2.7:

В Python перегрузка методов невозможна; если вы действительно хотите получить доступ к одной и той же функции с различными функциями, я предлагаю вам перейти к переопределению метода.

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

В Python перегрузка не является прикладной концепцией. Однако, если вы пытаетесь создать случай, когда, например, вы хотите, чтобы один инициализатор выполнялся, если передан аргумент типа, fooи другой инициализатор для аргумента типа bar, так как все в Python обрабатывается как объект, вы можете проверить имя типа класса переданного объекта и запись условной обработки, основанной на этом.

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

Эта концепция может быть применена к нескольким различным сценариям с помощью различных методов по мере необходимости.

С. Гамги
источник
7

В Python вы бы сделали это с аргументом по умолчанию.

class A:

    def stackoverflow(self, i=None):    
        if i == None:
            print 'first method'
        else:
            print 'second method',i
Джон Гейнс младший
источник
5

Просто наткнулся на этот https://github.com/bintoro/overloading.py для всех, кому это может быть интересно.

Из readme связанного репозитория:

Перегрузка - это модуль, который обеспечивает диспетчеризацию функций на основе типов и количества аргументов времени выполнения.

Когда вызывается перегруженная функция, диспетчер сравнивает предоставленные аргументы с доступными сигнатурами функций и вызывает реализацию, которая обеспечивает наиболее точное соответствие.

Характеристики

Проверка функций при регистрации и подробные правила разрешения гарантируют уникальный, четко определенный результат во время выполнения. Реализует кэширование разрешения функции для отличной производительности. Поддерживает необязательные параметры (значения по умолчанию) в сигнатурах функций. Оценивает как позиционные, так и ключевые аргументы при поиске наилучшего соответствия. Поддерживает резервные функции и выполнение общего кода. Поддерживает аргумент полиморфизм. Поддерживает классы и наследование, в том числе методы класса и методы static.

Марк Лоуренс
источник
3

Python не поддерживает перегрузку методов, как Java или C ++. Мы можем перегружать методы, но можем использовать только последний определенный метод.

# 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)

Нам нужно предоставить необязательные аргументы или * args, чтобы предоставить различное количество аргументов при вызове.

Предоставлено https://www.geeksforgeeks.org/python-method-overloading/

Atihska
источник
Это не перегрузка. Это называется перезаписью. Последний поддерживается Python. Первое можно реализовать с помощью декораторов.
Паеббельс
2

Python 3.x включает в себя стандартную библиотеку типов, которая позволяет перегружать методы с помощью декоратора @overload. К сожалению, это должно сделать код более читабельным, так как за декорированными методами @overload должен следовать не декорированный метод, который обрабатывает разные аргументы. Больше можно найти здесь, но для вашего примера:

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)
nickthefreak
источник
1
«The» в конце вашего ответа заставляет меня думать, что вы еще не закончили писать свой ответ. Пожалуйста, отредактируйте свой ответ, чтобы завершить его.
Artjom B.
0

В файле 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

В файле Main.py

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

Мы можем перегрузить метод с помощью multipledispatch

Mahabubuzzaman
источник
Это требует использования пакета multipledispatch ( pypi.org/project/multipledispatch ), который не является частью ядра python.
Энтони
0

Python добавил декоратор @overload с PEP-3124, чтобы обеспечить синтаксический сахар для перегрузки с помощью проверки типов - вместо того, чтобы просто работать с перезаписью.

Пример кода по перегрузке через @overload из 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

преобразуется @ overload-decorator в:

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
Nex
источник