Быстрый ответ: существует пакет перегрузки в PyPI, который реализует это более надежно, чем то, что я опишу ниже, хотя и использую немного другой синтаксис. Заявлено, что он работает только с Python 3, но похоже, что для его работы с Python 2 потребуются лишь небольшие изменения (если таковые имеются, я не пробовал).
Длинный ответ: В языках, где вы можете перегружать функции, имя функции (буквально или эффективно) дополнено информацией о сигнатуре ее типа, как когда функция определена, так и когда она называется. Когда компилятор или интерпретатор ищет определение функции, он использует как объявленное имя, так и типы параметров, чтобы определить, к какой функции обращаться. Таким образом, логический способ реализовать перегрузку в Python - реализовать оболочку, которая использует как объявленное имя, так и типы параметров для разрешения функции.
Вот простая реализация:
from collections import defaultdict
def determine_types(args, kwargs):
return tuple([type(a) for a in args]), \
tuple([(k, type(v)) for k,v in kwargs.iteritems()])
function_table = defaultdict(dict)
def overload(arg_types=(), kwarg_types=()):
def wrap(func):
named_func = function_table[func.__name__]
named_func[arg_types, kwarg_types] = func
def call_function_by_signature(*args, **kwargs):
return named_func[determine_types(args, kwargs)](*args, **kwargs)
return call_function_by_signature
return wrap
overload
должен вызываться с двумя необязательными аргументами: кортеж, представляющий типы всех позиционных аргументов, и кортеж кортежей, представляющий сопоставления типа имени всех аргументов ключевого слова. Вот пример использования:
>>> @overload((str, int))
... def f(a, b):
... return a * b
>>> @overload((int, int))
... def f(a, b):
... return a + b
>>> print f('a', 2)
aa
>>> print f(4, 2)
6
>>> @overload((str,), (('foo', int), ('bar', float)))
... def g(a, foo, bar):
... return foo*a + str(bar)
>>> @overload((str,), (('foo', float), ('bar', float)))
... def g(a, foo, bar):
... return a + str(foo*bar)
>>> print g('a', foo=7, bar=4.4)
aaaaaaa4.4
>>> print g('b', foo=7., bar=4.4)
b30.8
Недостатки этого включают
Фактически он не проверяет, совместима ли функция, к которой применяется декоратор, с аргументами, данными декоратору. Вы могли бы написать
@overload((str, int))
def h():
return 0
и вы получите ошибку при вызове функции.
Это не изящно обрабатывает случай, когда не существует перегруженной версии, соответствующей типам передаваемых аргументов (это поможет вызвать более описательную ошибку)
Различает именованные и позиционные аргументы, что-то вроде
g('a', 7, bar=4.4)
не работает.
- Есть много вложенных скобок, связанных с использованием этого, как в определениях для
g
.
- Как уже упоминалось в комментариях, это не касается функций с одинаковыми именами в разных модулях.
Все это может быть исправлено с достаточной тряпкой, я думаю. В частности, проблема конфликтов имен легко решается путем сохранения таблицы диспетчеризации в качестве атрибута функции, возвращаемой декоратором. Но, как я уже сказал, это простой пример, демонстрирующий основы того, как это сделать.