Перегрузка функции Python

213

Я знаю, что Python не поддерживает перегрузку методов, но я столкнулся с проблемой, которую не могу решить хорошим способом Pythonic.

Я делаю игру, в которой персонаж должен стрелять различными пулями, но как мне написать разные функции для создания этих пуль? Например, предположим, у меня есть функция, которая создает пулю, путешествующую из точки А в точку Б с заданной скоростью. Я бы написал такую ​​функцию:

    def add_bullet(sprite, start, headto, speed):
        ... Code ...

Но я хочу написать другие функции для создания пуль, такие как:

    def add_bullet(sprite, start, direction, speed):
    def add_bullet(sprite, start, headto, spead, acceleration):
    def add_bullet(sprite, script): # For bullets that are controlled by a script
    def add_bullet(sprite, curve, speed): # for bullets with curved paths
    ... And so on ...

И так со многими вариациями. Есть ли лучший способ сделать это, не используя так много ключевых аргументов, потому что это становится довольно быстро. Переименование каждой функции очень плохо тоже , потому что вы получаете либо add_bullet1, add_bullet2или add_bullet_with_really_long_name.

Чтобы ответить на некоторые ответы:

  1. Нет, я не могу создать иерархию класса Bullet, потому что это слишком медленно. Фактический код для управления маркерами находится на C, а мои функции - обертки вокруг C API.

  2. Я знаю об аргументах ключевых слов, но проверка на все виды комбинаций параметров становится раздражающей, но аргументы по умолчанию помогают выделить как acceleration=0

Пули
источник
5
Работает только для одного параметра, но здесь (для людей, которые приходят сюда из поисковой системы): docs.python.org/3/library/…
leewz
1
это похоже на хорошее место для значений по умолчанию. Вы можете установить None и просто проверить их. дополнительное логическое влияние кажется незначительным
Эндрю Скотт Эванс
Нужно использовать, default value + if + elseчтобы сделать то же самое, что и C ++. Это одна из немногих вещей, которые C ++ имеет лучшую читаемость, чем Python ...
Deqing
Я запутался, почему kwargs не является правильным ответом. Вы говорите, что не хотите использовать много аргументов ключевых слов, потому что это становится ужасно быстрым ... ну, в этом и заключается суть проблемы. Если у вас много аргументов, и это грязно, потому что у вас много аргументов, чем вы ожидали? Вы хотите использовать много аргументов, нигде не указывая их ??? Python не читатель разума.
Исчисление
Мы не знаем, что это за объекты, script, curveесть ли у них общий предок, какие методы они поддерживают. С помощью duck-typing вам нужно, чтобы дизайн класса выяснил, какие методы они должны поддерживать. Предположительно Scriptподдерживает своего рода обратный вызов на основе временного шага (но какой объект должен возвращать «позицию на этом временном шаге» траекторию на этом временном шаге »). Предположительно, start, direction, speedи start, headto, spead, accelerationоба описывают типы траекторий, но опять же, вы должны разработать принимающий класс, чтобы знать, как распаковать его и обработать.
SMCI

Ответы:

144

То, что вы просите, называется многократной отправкой . Смотрите примеры языка Julia, которые демонстрируют различные типы рассылок.

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

Почему бы не перегружать?

Во-первых, нужно понять концепцию перегрузки и почему она не применима к питону.

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

Python является динамически типизированным языком, поэтому концепция перегрузки просто к нему не относится. Однако еще не все потеряно, так как мы можем создавать такие альтернативные функции во время выполнения:

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

Таким образом, мы должны быть в состоянии сделать мультиметоды в Python - или, как это называется альтернативно: множественная диспетчеризация .

Многократная отправка

Мультиметоды также называют многократной отправкой :

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

Python не поддерживает это из коробки 1 , но, как это бывает, есть отличный пакет python, называемый multipledispatch, который делает именно это.

Решение

Вот как мы можем использовать пакет multipledispatch 2 для реализации ваших методов:

>>> from multipledispatch import dispatch
>>> from collections import namedtuple  
>>> from types import *  # we can test for lambda type, e.g.:
>>> type(lambda a: 1) == LambdaType
True

>>> Sprite = namedtuple('Sprite', ['name'])
>>> Point = namedtuple('Point', ['x', 'y'])
>>> Curve = namedtuple('Curve', ['x', 'y', 'z'])
>>> Vector = namedtuple('Vector', ['x','y','z'])

>>> @dispatch(Sprite, Point, Vector, int)
... def add_bullet(sprite, start, direction, speed):
...     print("Called Version 1")
...
>>> @dispatch(Sprite, Point, Point, int, float)
... def add_bullet(sprite, start, headto, speed, acceleration):
...     print("Called version 2")
...
>>> @dispatch(Sprite, LambdaType)
... def add_bullet(sprite, script):
...     print("Called version 3")
...
>>> @dispatch(Sprite, Curve, int)
... def add_bullet(sprite, curve, speed):
...     print("Called version 4")
...

>>> sprite = Sprite('Turtle')
>>> start = Point(1,2)
>>> direction = Vector(1,1,1)
>>> speed = 100 #km/h
>>> acceleration = 5.0 #m/s
>>> script = lambda sprite: sprite.x * 2
>>> curve = Curve(3, 1, 4)
>>> headto = Point(100, 100) # somewhere far away

>>> add_bullet(sprite, start, direction, speed)
Called Version 1

>>> add_bullet(sprite, start, headto, speed, acceleration)
Called version 2

>>> add_bullet(sprite, script)
Called version 3

>>> add_bullet(sprite, curve, speed)
Called version 4

1. Python 3 в настоящее время поддерживает однократную отправку
2. Старайтесь не использовать multipledispatch в многопоточной среде, иначе вы получите странное поведение.

Андрей Дроздюк
источник
6
В чем проблема с «multipledispatch» в многопоточной среде? Поскольку код на стороне сервера обычно находится в многопоточной среде! Просто пытаюсь откопать это!
danzeer
7
@danzeer Это не было потокобезопасным. Я видел, как аргумент был изменен двумя разными потоками (то есть значение speedмогло бы измениться в середине функции, когда другой поток устанавливает свое собственное значение speed) !!! Мне потребовалось много времени, чтобы понять, что именно библиотека была виновником.
Андрей Дроздюк
108

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

class Character(object):
    # your character __init__ and other methods go here

    def add_bullet(self, sprite=default, start=default, 
                 direction=default, speed=default, accel=default, 
                  curve=default):
        # do stuff with your arguments

В приведенном выше коде defaultдопустимое значение по умолчанию для этих аргументов, или None. Затем вы можете вызвать метод только с интересующими вас аргументами, и Python будет использовать значения по умолчанию.

Вы также можете сделать что-то вроде этого:

class Character(object):
    # your character __init__ and other methods go here

    def add_bullet(self, **kwargs):
        # here you can unpack kwargs as (key, values) and
        # do stuff with them, and use some global dictionary
        # to provide default values and ensure that ``key``
        # is a valid argument...

        # do stuff with your arguments

Другая альтернатива - напрямую подключить нужную функцию к классу или экземпляру:

def some_implementation(self, arg1, arg2, arg3):
  # implementation
my_class.add_bullet = some_implementation_of_add_bullet

Еще один способ - использовать абстрактный шаблон фабрики:

class Character(object):
   def __init__(self, bfactory, *args, **kwargs):
       self.bfactory = bfactory
   def add_bullet(self):
       sprite = self.bfactory.sprite()
       speed = self.bfactory.speed()
       # do stuff with your sprite and speed

class pretty_and_fast_factory(object):
    def sprite(self):
       return pretty_sprite
    def speed(self):
       return 10000000000.0

my_character = Character(pretty_and_fast_factory(), a1, a2, kw1=v1, kw2=v2)
my_character.add_bullet() # uses pretty_and_fast_factory

# now, if you have another factory called "ugly_and_slow_factory" 
# you can change it at runtime in python by issuing
my_character.bfactory = ugly_and_slow_factory()

# In the last example you can see abstract factory and "method
# overloading" (as you call it) in action 
Escualo
источник
107
Все это выглядит как примеры переменных аргументов, а не перегрузки. Поскольку перегрузка позволяет вам иметь одну и ту же функцию, для разных типов в качестве аргументов. например: sum (real_num1, real_num2) и sum (imaginary_num1, imaary_num2) оба будут иметь один и тот же синтаксис вызова, но на самом деле ожидают 2 разных типа в качестве входных данных, и реализация должна также измениться внутренне
Efren
17
Используя ответ, с которым вы пошли бы, как бы вы представили вызывающей стороне, какие аргументы имеют смысл вместе? Простая установка набора аргументов, каждый из которых имеет значение по умолчанию, может обеспечить такую ​​же функциональность, но с точки зрения API это гораздо менее элегантно
Грег Эннис,
6
Ни одно из вышеперечисленного не является перегрузкой, реализация должна будет проверять все комбинации входных параметров (или игнорировать параметры), например: if sprite and script and not start and not direction and not speed...просто знать, что это в конкретном действии. потому что вызывающий абонент может вызвать функцию, предоставив все доступные параметры. Во время перегрузки определите для вас точные наборы соответствующих параметров.
Роу Гавирель
5
Очень расстраивает, когда люди говорят, что python поддерживает перегрузку методов. Это не. Тот факт, что вы помещаете «перегрузку метода» в кавычки, означает, что вы знаете об этом факте. Вы можете получить похожую функциональность с помощью нескольких методов, таких как упомянутый здесь. Но перегрузка метода имеет очень конкретное определение.
Говард Своп
Я думаю, что цель заключается в том, что хотя перегрузка методов не является особенностью Python, вышеупомянутые механизмы могут быть использованы для достижения эквивалентного эффекта.
Rawr позвонил
93

Для перегрузки функций вы можете использовать решение «по своему усмотрению». Этот скопирован из статьи Гвидо ван Россума о мультиметодах (потому что разница между mm и перегрузкой в ​​python невелика):

registry = {}

class MultiMethod(object):
    def __init__(self, name):
        self.name = name
        self.typemap = {}
    def __call__(self, *args):
        types = tuple(arg.__class__ for arg in args) # a generator expression!
        function = self.typemap.get(types)
        if function is None:
            raise TypeError("no match")
        return function(*args)
    def register(self, types, function):
        if types in self.typemap:
            raise TypeError("duplicate registration")
        self.typemap[types] = function


def multimethod(*types):
    def register(function):
        name = function.__name__
        mm = registry.get(name)
        if mm is None:
            mm = registry[name] = MultiMethod(name)
        mm.register(types, function)
        return mm
    return register

Использование будет

from multimethods import multimethod
import unittest

# 'overload' makes more sense in this case
overload = multimethod

class Sprite(object):
    pass

class Point(object):
    pass

class Curve(object):
    pass

@overload(Sprite, Point, Direction, int)
def add_bullet(sprite, start, direction, speed):
    # ...

@overload(Sprite, Point, Point, int, int)
def add_bullet(sprite, start, headto, speed, acceleration):
    # ...

@overload(Sprite, str)
def add_bullet(sprite, script):
    # ...

@overload(Sprite, Curve, speed)
def add_bullet(sprite, curve, speed):
    # ...

Наиболее ограничительные ограничения на данный момент :

  • методы не поддерживаются, только функции, которые не являются членами класса;
  • наследование не обрабатывается;
  • kwargs не поддерживаются;
  • регистрация новых функций должна быть сделана во время импорта, что не является потокобезопасным
Александр Полуэктов
источник
6
+1 для декораторов за расширение языка в этом случае использования.
Элоимс
1
+1, потому что это отличная идея (и, вероятно, с чем должен идти OP) --- Я никогда не видел мультиметодную реализацию в Python.
Escualo
39

Возможный вариант - использовать модуль multipledispatch, как описано здесь: http://matthewrocklin.com/blog/work/2014/02/25/Multiple-Dispatch

Вместо этого:

def add(self, other):
    if isinstance(other, Foo):
        ...
    elif isinstance(other, Bar):
        ...
    else:
        raise NotImplementedError()

Ты можешь сделать это:

from multipledispatch import dispatch
@dispatch(int, int)
def add(x, y):
    return x + y    

@dispatch(object, object)
def add(x, y):
    return "%s + %s" % (x, y)

В результате использования:

>>> add(1, 2)
3

>>> add(1, 'hello')
'1 + hello'
Дейв С
источник
4
Почему это не получает больше голосов? Я предполагаю из-за отсутствия примеров ... Я создал ответ с примером того, как реализовать решение проблемы OP с помощью пакета multipledispatch .
Андрей Дроздюк
19

В Python 3.4 был добавлен PEP-0443. Универсальные функции единой отправки .

Вот краткое описание API от PEP.

Чтобы определить универсальную функцию, украсьте ее с помощью декоратора @singledispatch. Обратите внимание, что отправка происходит по типу первого аргумента. Создайте свою функцию соответственно:

from functools import singledispatch
@singledispatch
def fun(arg, verbose=False):
    if verbose:
        print("Let me just say,", end=" ")
    print(arg)

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

@fun.register(int)
def _(arg, verbose=False):
    if verbose:
        print("Strength in numbers, eh?", end=" ")
    print(arg)

@fun.register(list)
def _(arg, verbose=False):
    if verbose:
        print("Enumerate this:")
    for i, elem in enumerate(arg):
        print(i, elem)
SKhalymon
источник
11

Этот тип поведения обычно решается (на языках ООП) с использованием полиморфизма. Каждый тип пули будет отвечать за знание того, как она движется. Например:

class Bullet(object):
    def __init__(self):
        self.curve = None
        self.speed = None
        self.acceleration = None
        self.sprite_image = None

class RegularBullet(Bullet):
    def __init__(self):
        super(RegularBullet, self).__init__()
        self.speed = 10

class Grenade(Bullet):
    def __init__(self):
        super(Grenade, self).__init__()
        self.speed = 4
        self.curve = 3.5

add_bullet(Grendade())

def add_bullet(bullet):
    c_function(bullet.speed, bullet.curve, bullet.acceleration, bullet.sprite, bullet.x, bullet.y) 


void c_function(double speed, double curve, double accel, char[] sprite, ...) {
    if (speed != null && ...) regular_bullet(...)
    else if (...) curved_bullet(...)
    //..etc..
}

Передайте как можно больше аргументов в c_function, а затем определите, какую функцию c вызывать, основываясь на значениях в исходной функции c. Таким образом, python должен вызывать только одну функцию c. Эта одна функция c смотрит на аргументы, а затем может соответствующим образом делегировать другим функциям c.

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

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

Джош Смитон
источник
1
Это был мой первоначальный подход, но по соображениям производительности мне пришлось переписать этот код на C.
Bullets
@ Bullets, я хотел бы предположить, что может быть несколько различных вариантов, доступных для улучшения производительности, вместо того, чтобы писать множество функций c, которые, вероятно, не будут делать много. Например: создание экземпляра может быть дорогим, поэтому поддерживайте пул объектов. Хотя я говорю это, не зная, что вы нашли слишком медленно. Из интереса, что именно было медленным в этом подходе? Если значительное время не будет потрачено на стороне C границы, я не могу думать, что Python (сам по себе) является реальной проблемой.
Джош Смитон
Возможно, есть и другие способы улучшить производительность, но я гораздо лучше с C, чем с Python. Проблема заключалась в том, чтобы рассчитать движение пуль и определить, когда они выходят за границы экрана. У меня были методы для расчета положения пули, pos+v*tа затем сравнения с границами экрана if x > 800и так далее. Вызов этих функций несколько сотен раз за кадр оказался недопустимо медленным. Это было что-то вроде 40 кадров в секунду на 100% процессора с чистым питоном до 60 кадров в секунду с 5% -10%, когда сделано в C.
Пули
@ Bullets, достаточно справедливо тогда. Я бы по-прежнему использовал подход, который использовался для инкапсуляции данных. Передайте экземпляр маркера add_bulletи извлеките все поля, которые вам нужны. Я отредактирую свой ответ.
Джош Смитон
@ Bullets: Вы можете объединить свои функции C и подход ООП, предложенный Джошем, используя Cython . Это позволяет раннее связывание, поэтому не должно быть штраф за скорость.
JFS
4

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

Игнасио Васкес-Абрамс
источник
Я собирался предложить второй подход: сделать несколько классов BulletParams ..., чтобы указать детали маркера.
Джон Цвинк
Вы можете остановиться на этом? Я пытался создать иерархию классов с разными маркерами, но это не работает, потому что Python слишком медленный. Он не может рассчитать движение необходимого количества пуль достаточно быстро, поэтому мне пришлось написать эту часть на C. Все варианты add_bullet просто вызывают соответствующую функцию C.
Пули
4

Я думаю, что ваше основное требование - иметь синтаксис, подобный C / C ++, в python с минимально возможной головной болью. Хотя мне понравился ответ Александра Полуэктова, он не работает на уроках.

Следующее должно работать для классов. Он работает, различая количество аргументов без ключевых слов (но не поддерживает различение по типу):

class TestOverloading(object):
    def overloaded_function(self, *args, **kwargs):
        # Call the function that has the same number of non-keyword arguments.  
        getattr(self, "_overloaded_function_impl_" + str(len(args)))(*args, **kwargs)
    
    def _overloaded_function_impl_3(self, sprite, start, direction, **kwargs):
        print "This is overload 3"
        print "Sprite: %s" % str(sprite)
        print "Start: %s" % str(start)
        print "Direction: %s" % str(direction)
        
    def _overloaded_function_impl_2(self, sprite, script):
        print "This is overload 2"
        print "Sprite: %s" % str(sprite)
        print "Script: "
        print script

И это можно использовать просто так:

test = TestOverloading()

test.overloaded_function("I'm a Sprite", 0, "Right")
print
test.overloaded_function("I'm another Sprite", "while x == True: print 'hi'")

Вывод:

Это перегрузка 3
Sprite: я Sprite
Начало: 0
Направление: вправо

Это перегрузка 2
Sprite: я другой
сценарий Sprite :
пока x == True: напечатать 'hi'

Тим Людвински
источник
4

@overloadДекоратора был добавлен с оттенками типа (PEP 484). Хотя это не меняет поведение python, оно облегчает понимание происходящего и позволяет mypy обнаруживать ошибки.
Смотрите: Тип подсказки и PEP 484

Ричард Уайтхед
источник
Можете ли вы добавить несколько примеров?
геррит
3

Я думаю, что Bulletиерархия классов с ассоциированным полиморфизмом - это путь. Вы можете эффективно перегрузить конструктор базового класса, используя метакласс, так что вызов базового класса приводит к созданию соответствующего объекта подкласса. Ниже приведен пример кода для иллюстрации сути того, что я имею в виду.

обновленный

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

Для достижения этой цели BulletMetaBaseэкземпляр BulletMetaкласса создается путем явного вызова метакласса при создании Bulletбазового класса (вместо использования __metaclass__=атрибута класса или metaclassаргумента ключевого слова в зависимости от версии Python).

class BulletMeta(type):
    def __new__(cls, classname, bases, classdict):
        """ Create Bullet class or a subclass of it. """
        classobj = type.__new__(cls, classname, bases, classdict)
        if classname != 'BulletMetaBase':
            if classname == 'Bullet':  # Base class definition?
                classobj.registry = {}  # Initialize subclass registry.
            else:
                try:
                    alias = classdict['alias']
                except KeyError:
                    raise TypeError("Bullet subclass %s has no 'alias'" %
                                    classname)
                if alias in Bullet.registry: # unique?
                    raise TypeError("Bullet subclass %s's alias attribute "
                                    "%r already in use" % (classname, alias))
                # Register subclass under the specified alias.
                classobj.registry[alias] = classobj

        return classobj

    def __call__(cls, alias, *args, **kwargs):
        """ Bullet subclasses instance factory.

            Subclasses should only be instantiated by calls to the base
            class with their subclass' alias as the first arg.
        """
        if cls != Bullet:
            raise TypeError("Bullet subclass %r objects should not to "
                            "be explicitly constructed." % cls.__name__)
        elif alias not in cls.registry: # Bullet subclass?
            raise NotImplementedError("Unknown Bullet subclass %r" %
                                      str(alias))
        # Create designated subclass object (call its __init__ method).
        subclass = cls.registry[alias]
        return type.__call__(subclass, *args, **kwargs)


class Bullet(BulletMeta('BulletMetaBase', (object,), {})):
    # Presumably you'd define some abstract methods that all here
    # that would be supported by all subclasses.
    # These definitions could just raise NotImplementedError() or
    # implement the functionality is some sub-optimal generic way.
    # For example:
    def fire(self, *args, **kwargs):
        raise NotImplementedError(self.__class__.__name__ + ".fire() method")

    # Abstract base class's __init__ should never be called.
    # If subclasses need to call super class's __init__() for some
    # reason then it would need to be implemented.
    def __init__(self, *args, **kwargs):
        raise NotImplementedError("Bullet is an abstract base class")


# Subclass definitions.
class Bullet1(Bullet):
    alias = 'B1'
    def __init__(self, sprite, start, direction, speed):
        print('creating %s object' % self.__class__.__name__)
    def fire(self, trajectory):
        print('Bullet1 object fired with %s trajectory' % trajectory)


class Bullet2(Bullet):
    alias = 'B2'
    def __init__(self, sprite, start, headto, spead, acceleration):
        print('creating %s object' % self.__class__.__name__)


class Bullet3(Bullet):
    alias = 'B3'
    def __init__(self, sprite, script): # script controlled bullets
        print('creating %s object' % self.__class__.__name__)


class Bullet4(Bullet):
    alias = 'B4'
    def __init__(self, sprite, curve, speed): # for bullets with curved paths
        print('creating %s object' % self.__class__.__name__)


class Sprite: pass
class Curve: pass

b1 = Bullet('B1', Sprite(), (10,20,30), 90, 600)
b2 = Bullet('B2', Sprite(), (-30,17,94), (1,-1,-1), 600, 10)
b3 = Bullet('B3', Sprite(), 'bullet42.script')
b4 = Bullet('B4', Sprite(), Curve(), 720)
b1.fire('uniform gravity')
b2.fire('uniform gravity')

Вывод:

creating Bullet1 object
creating Bullet2 object
creating Bullet3 object
creating Bullet4 object
Bullet1 object fired with uniform gravity trajectory
Traceback (most recent call last):
  File "python-function-overloading.py", line 93, in <module>
    b2.fire('uniform gravity') # NotImplementedError: Bullet2.fire() method
  File "python-function-overloading.py", line 49, in fire
    raise NotImplementedError(self.__class__.__name__ + ".fire() method")
NotImplementedError: Bullet2.fire() method
Мартино
источник
Хм, это все еще причудливый способ назвать функции как add_bullet1, add_bullet2 и так далее.
Пули
1
@ Bullets: Возможно, это так, или, может быть, это просто немного сложный способ создания фабричной функции. Приятно то, что он поддерживает иерархию Bulletподклассов без необходимости изменять базовый класс или функцию фабрики каждый раз, когда вы добавляете другой подтип. (Конечно, если вы используете C, а не C ++, я думаю, у вас нет классов.) Вы также можете создать более умный метакласс, который сам определит, какой подкласс создать, основываясь на типе и / или числе. переданных аргументов (как в C ++ для поддержки перегрузки).
Мартино
1
Эта идея наследования была бы моим первым вариантом.
Даниэль Мёллер
3

Python 3.8 добавил functools.singledispatchmethod

Преобразуйте метод в универсальную функцию с одной отправкой.

Чтобы определить универсальный метод, украсьте его с помощью декоратора @singledispatchmethod. Обратите внимание, что отправка происходит по типу первого аргумента не-self или non-cls, соответственно создайте свою функцию:

from functools import singledispatchmethod


class Negator:
    @singledispatchmethod
    def neg(self, arg):
        raise NotImplementedError("Cannot negate a")

    @neg.register
    def _(self, arg: int):
        return -arg

    @neg.register
    def _(self, arg: bool):
        return not arg


negator = Negator()
for v in [42, True, "Overloading"]:
    neg = negator.neg(v)
    print(f"{v=}, {neg=}")

Вывод

v=42, neg=-42
v=True, neg=False
NotImplementedError: Cannot negate a

@singledispatchmethod поддерживает вложение с другими декораторами, такими как @classmethod. Обратите внимание, что для обеспечения dispatcher.register, метод singledispatch должен быть внешним декоратором. Вот класс Negator с привязанными к классу методами neg:

from functools import singledispatchmethod


class Negator:
    @singledispatchmethod
    @staticmethod
    def neg(arg):
        raise NotImplementedError("Cannot negate a")

    @neg.register
    def _(arg: int) -> int:
        return -arg

    @neg.register
    def _(arg: bool) -> bool:
        return not arg


for v in [42, True, "Overloading"]:
    neg = Negator.neg(v)
    print(f"{v=}, {neg=}")

Вывод:

v=42, neg=-42
v=True, neg=False
NotImplementedError: Cannot negate a

Тот же шаблон может быть использован для других подобных декораторов: staticmethod, abstractmethod и других.

Влад Безден
источник
2

Используйте ключевые аргументы со значениями по умолчанию. Например

def add_bullet(sprite, start=default, direction=default, script=default, speed=default):

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

Рэйф Кеттлер
источник
2

перегрузка методов сложна в питоне. Однако может использоваться передача переменных dict, list или primitive.

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

Давайте возьмем ваш пример:

метод перегрузки класса с вызовом методов из другого класса.

def add_bullet(sprite=None, start=None, headto=None, spead=None, acceleration=None):

передать аргументы из удаленного класса:

add_bullet(sprite = 'test', start=Yes,headto={'lat':10.6666,'long':10.6666},accelaration=10.6}

ИЛИ

add_bullet(sprite = 'test', start=Yes, headto={'lat':10.6666,'long':10.6666},speed=['10','20,'30']}

Таким образом, достигается обработка для списка, словаря или примитивных переменных из-за перегрузки метода.

попробуйте это для ваших кодов.

shashankS
источник
2

Просто простой декоратор

class overload:
    def __init__(self, f):
        self.cases = {}

    def args(self, *args):
        def store_function(f):
            self.cases[tuple(args)] = f
            return self
        return store_function

    def __call__(self, *args):
        function = self.cases[tuple(type(arg) for arg in args)]
        return function(*args)

Вы можете использовать это так

@overload
def f():
    pass

@f.args(int, int)
def f(x, y):
    print('two integers')

@f.args(float)
def f(x):
    print('one float')


f(5.5)
f(1, 2)

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

Уточнение понятий

  • диспетчеризация функции : есть несколько функций с одинаковым именем. Какой из них должен быть назван? две стратегии
  • статическая отправка во время компиляции ( «перегрузка» ). решить, какую функцию вызывать, основываясь на типе аргументов во время компиляции . Во всех динамических языках нет типа времени компиляции, поэтому перегрузка невозможна по определению
  • динамическая / динамическая отправка : решите, какую функцию вызывать, основываясь на типе аргументов во время выполнения . Это то, что делают все языки ООП: несколько классов имеют одинаковые методы, и язык решает, какой из них вызывать, основываясь на типе self/thisаргумента. Тем не менее, большинство языков делают это только для thisаргумента. Вышеуказанный декоратор распространяет идею на несколько параметров.

Чтобы разобраться, предположим статический язык и определить функции

void f(Integer x):
    print('integer called')

void f(Float x):
    print('float called')

void f(Number x):
    print('number called')


Number x = new Integer('5')
f(x)
x = new Number('3.14')
f(x)

При статической диспетчеризации (перегрузке) вы увидите «номер, вызываемый» дважды, потому что xон был объявлен как Number, и это все , что нужно для перегрузки. С динамической диспетчеризацией вы увидите «целое число вызвано, поплавок вызвано», потому что это фактические типы xв момент вызова функции.

blue_note
источник
Этот пример принципиально не иллюстрирует, какой метод был вызван xдля динамической диспетчеризации, и в каком порядке оба метода были вызваны для статической диспетчеризации. Рекомендую вам отредактировать печатные заявления и print('number called for Integer')т. Д.
smci