Определение "перегруженных" функций в 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__), но так как ваш оператор работает только для объектов одного типа, он здесь бесполезен.

Другие вопросы по тегам