Вопрос

I really like the syntax of the "magic methods" or whatever they are called in Python, like

class foo:
    def __add__(self,other): #It can be called like c = a + b
        pass

The call

c = a + b

is then translated to

a.__add__(b)

Is it possible to mimic such behaviour for "non-magic" functions? In numerical computations I need the Kronecker product, and am eager to have "kron" function such that

kron(a,b) 

is in fact

a.kron(b)?

The use case is: I have two similar classes, say, matrix and vector, both having Kronecker product. I would like to call them

a = matrix()
b = matrix()
c = kron(a,b)

a = vector()
b = vector()
c = kron(a,b)

matrix and vector classes are defined in one .py file, thus share the common namespace. So, what is the best (Pythonic?) way to implement functions like above? Possible solutions:

1) Have one kron() functions and do type check

2) Have different namespaces

3) ?

Это было полезно?

Решение

The python default operator methods (__add__ and such) are hard-wired; python will look for them because the operator implementations look for them.

However, there is nothing stopping you from defining a kron function that does the same thing; look for __kron__ or __rkron__ on the objects passed to it:

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)

Другие советы

What you're describing is multiple dispatch or multimethods. Magic methods is one way to implement them, but it's actually more usual to have an object that you can register type-specific implementations on.

For example, http://pypi.python.org/pypi/multimethod/ will let you write

@multimethod(matrix, matrix)
def kron(lhs, rhs):
    pass

@multimethod(vector, vector)
def kron(lhs, rhs):
    pass

It's quite easy to write a multimethod decorator yourself; the BDFL describes a typical implementation in an article. The idea is that the multimethod decorator associates the type signature and method with the method name in a registry, and replaces the method with a generated method that performs type lookup to find the best match.

Technically speaking, implementing something similar to the "standard" operator (and operator-like - think len() etc) behaviour is not difficult:

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")

Now you just have to add a __kron__(self, other) method on the relevant types (assuming you have control over these types or they don't use slots or whatever else that would prevent adding methods outside the class statement's body).

Now I'd not use a __magic__ naming scheme as in my above snippet since this is supposed to be reserved for the language itself.

Another solution would be to maintain a type:specifici function mapping and have the "generic" kron function looking up the mapping, ie:

# 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")

This solution doesn't work well with inheritance but it "less surprinsing" - doesn't require monkeypatching nor __magic__ name etc.

I think having one single function that delegate the actual computation is a nice way to do it. If the Kronecker product only works on two similar classes, you can even do the type checking in the function :

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)

Then, you just need to define a _kron_ method on the class. This is only some basic example, you might want to improve it to handle more gracefully the cases where a class doesn't have the _kron_ method, or to handle subclasses.

Binary operations in the standart libary usually have a reverse dual (__add__ and __radd__), but since your operator only work for same type objects, it isn't useful here.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top