Я знаю, что Python не поддерживает перегрузку методов, но я столкнулся с проблемой, которую, похоже, не могу решить приятным питоновским способом.
Я создаю игру, в которой персонажу нужно стрелять различными пулями, но как мне написать различные функции для создания этих пуль? Например, предположим, что у меня есть функция, которая создает маркер, перемещающийся из точки A в B с заданной скоростью. Я бы написал функцию, подобную этой:
Но я хочу написать другие функции для создания маркеров, таких как:
defadd_bullet(sprite, start, direction, speed): defadd_bullet(sprite, start, headto, spead, acceleration): defadd_bullet(sprite, script): # For bullets that are controlled by a script defadd_bullet(sprite, curve, speed): # for bullets with curved paths # And so on ...
И так далее со многими вариациями. Есть ли лучший способ сделать это без использования такого количества аргументов ключевого слова, потому что это быстро становится уродливым. Переименовывать каждую функцию тоже довольно плохо, потому что вы получаете либо add_bullet1, add_bullet2 , либо add_bullet_with_really_long_name.
Чтобы ответить на некоторые вопросы:
Нет, я не могу создать иерархию маркерных классов, потому что это слишком медленно. Фактический код для управления маркерами написан на C, а мои функции являются оболочками вокруг C API.
Я знаю о аргументах ключевого слова, но проверка всевозможных комбинаций параметров начинает раздражать, но аргументы по умолчанию помогают выделить такие acceleration=0
Переведено автоматически
Ответ 1
То, что вы запрашиваете, называется множественной отправкой. Смотрите примеры языка Julia, которые демонстрируют различные типы отправок.
Однако, прежде чем рассматривать это, мы сначала разберемся, почему перегрузка на самом деле не то, что вы хотите в Python.
Почему не перегрузка?
Во-первых, нужно понять концепцию перегрузки и почему она неприменима к Python.
При работе с языками, которые могут различать типы данных во время компиляции, выбор между альтернативами может происходить во время компиляции. Процесс создания таких альтернативных функций для выбора во время компиляции обычно называется перегрузкой функции. (Википедия)
Python - это динамически типизированный язык, поэтому концепция перегрузки к нему просто неприменима. Однако не все потеряно, поскольку мы можем создавать такие альтернативные функции во время выполнения:
В языках программирования, которые откладывают идентификацию типа данных до времени выполнения, выбор между альтернативными функциями должен происходить во время выполнения на основе динамически определяемых типов аргументов функций. Функции, альтернативные реализации которых выбираются таким образом, чаще всего называются мультиметодами. (Википедия)
Итак, мы должны уметь выполнять мультиметоды на Python — или, как это альтернативно называется: множественная отправка.
Множественная отправка
Мультиметоды также называются множественной отправкой:
Множественная отправка или мультиметоды - это особенность некоторых объектно-ориентированных языков программирования, в которых функция или метод могут быть динамически отправлены на основе типа времени выполнения (dynamic) более чем одного из их аргументов. (Википедия)
Python не поддерживает это из коробки1, но, как оказалось, существует отличный пакет Python под названием multipledispatch, который делает именно это.
Решение
Вот как мы могли бы использовать пакет multipledispatch2 для реализации ваших методов:
>>> 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
Будьте осторожны, чтобы не использовать multipledispatch в многопоточной среде, иначе вы получите странное поведение.
Ответ 2
Python поддерживает "перегрузку методов" в том виде, в каком вы ее представляете. Фактически, то, что вы только что описали, тривиально реализовать на Python множеством различных способов, но я бы выбрал:
classCharacter(object): # your character __init__ and other methods go here
defadd_bullet(self, sprite=default, start=default, direction=default, speed=default, accel=default, curve=default): # do stuff with your arguments
В приведенном выше коде, default является вероятным значением по умолчанию для этих аргументов, или None. Затем вы можете вызвать метод только с теми аргументами, которые вас интересуют, и Python будет использовать значения по умолчанию.
Вы также могли бы сделать что-то подобное:
classCharacter(object): # your character __init__ and other methods go here
defadd_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
Другой альтернативой является прямое подключение нужной функции непосредственно к классу или экземпляру:
# 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
Ответ 3
Вы можете использовать "собственное решение" для перегрузки функций. Это скопировано из статьи Гвидо ван Россума о мультиметодах (потому что разница между мультиметодами и перегрузкой в Python невелика):
registry = {}
classMultiMethod(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 isNone: raise TypeError("no match") return function(*args) defregister(self, types, function): if types in self.typemap: raise TypeError("duplicate registration") self.typemap[types] = function
defmultimethod(*types): defregister(function): name = function.__name__ mm = registry.get(name) if mm isNone: mm = registry[name] = MultiMethod(name) mm.register(types, function) return mm return register
The usage would be
from multimethods import multimethod import unittest
# 'overload' makes more sense in this case overload = multimethod
classSprite(object): pass
classPoint(object): pass
classCurve(object): pass
@overload(Sprite, Point, Direction, int) defadd_bullet(sprite, start, direction, speed): # ...
@overload(Sprite, Point, Point, int, int) defadd_bullet(sprite, start, headto, speed, acceleration): # ...