Определение "перегруженных" функций в python
Мне очень нравится синтаксис "магических методов" или как они называются в Python, например
class foo:
def __add__(self,other): #It can be called like c = a + b
pass
Вызов
c = a + b
затем переводится в
a.__add__(b)
Можно ли имитировать такое поведение для "немагических" функций? В числовых вычислениях мне нужен продукт Кронекера, и я хочу иметь функцию "Крон", чтобы
kron(a,b)
на самом деле
a.kron(b)?
Вариант использования: у меня есть два схожих класса, скажем, матрица и вектор, оба имеют произведение Кронекера. Я хотел бы позвонить им
a = matrix()
b = matrix()
c = kron(a,b)
a = vector()
b = vector()
c = kron(a,b)
Матрица и векторные классы определены в одном файле.py, таким образом, совместно используют общее пространство имен. Итак, каков наилучший (Pythonic?) Способ реализации функций, как указано выше? Возможные решения:
1) Имейте одну функцию kron() и проверяйте тип
2) Иметь разные пространства имен
3)
4 ответа
Методы оператора Python по умолчанию (__add__
и такие) зашиты; Python будет искать их, потому что реализации операторов ищут их.
Однако ничто не мешает вам определить kron
функция, которая делает то же самое; Ищу __kron__
или же __rkron__
по объектам, переданным ему:
def kron(a, b):
if hasattr(a, '__kron__'):
return a.__kron__(b)
if hasattr(b, '__rkron__'):
return b.__rkron__(a)
# Default kron implementation here
return complex_operation_on_a_and_b(a, b)
То, что вы описываете, это множественная отправка или мультиметоды. Магические методы - это один из способов их реализации, но на самом деле более обычно иметь объект, на котором вы можете зарегистрировать специфичные для типа реализации.
Например, http://pypi.python.org/pypi/multimethod/ позволит вам написать
@multimethod(matrix, matrix)
def kron(lhs, rhs):
pass
@multimethod(vector, vector)
def kron(lhs, rhs):
pass
Довольно просто написать мультиметодный декоратор; BDFL описывает типичную реализацию в статье. Идея в том, что multimethod
decorator связывает сигнатуру и метод типа с именем метода в реестре и заменяет метод сгенерированным методом, который выполняет поиск типа для поиска наилучшего соответствия.
Технически говоря, реализация чего-то похожего на "стандартный" оператор (и похожий на оператор - думаю len()
и т. д.) поведение не сложное:
def kron(a, b):
if hasattr(a, '__kron__'):
return a.__kron__(b)
elif hasattr(b, '__kron__'):
return b.__kron__(a)
else:
raise TypeError("your error message here")
Теперь вам просто нужно добавить __kron__(self, other)
метод для соответствующих типов (при условии, что у вас есть контроль над этими типами, или они не используют слоты или что-то еще, что мешало бы добавлять методы вне тела оператора класса).
Теперь я не буду использовать __magic__
схема именования, как в приведенном выше фрагменте, так как предполагается, что это зарезервировано для самого языка.
Другим решением было бы сохранить type
:specifici function
отображение и есть "универсальный" kron
функция, ищущая отображение, то есть:
# kron.py
from somewhere import Matrix, Vector
def matrix_kron(a, b):
# code here
def vector_kron(a, b):
# code here
KRON_IMPLEMENTATIONS = dict(
Matrix=matrix_kron,
Vector=vector_kron,
)
def kron(a, b):
for typ in (type(a), type(b)):
implementation = KRON_IMPLEMENTATION.get(typ, None)
if implementation:
return implementation(a, b)
else:
raise TypeError("your message here")
Это решение не очень хорошо работает с наследованием, но оно "менее удивительно" - не требует ни обезьяньих патчей, ни __magic__
имя и т. д.
Я думаю, что наличие единственной функции, которая делегирует фактические вычисления, является хорошим способом сделать это. Если продукт Kronecker работает только в двух похожих классах, вы можете даже выполнить проверку типов в функции:
def kron(a, b):
if type(a) != type(b):
raise TypeError('expected two instances of the same class, got %s and %s'%(type(a), type(b)))
return a._kron_(b)
Затем вам просто нужно определить _kron_
метод на занятии. Это только базовый пример, вы можете улучшить его, чтобы более аккуратно обрабатывать случаи, когда класс не имеет _kron_
метод или для обработки подклассов.
Бинарные операции в стандартной библиотеке обычно имеют обратную двойную__add__
а также __radd__
), но так как ваш оператор работает только для объектов одного типа, он здесь бесполезен.