castle-bravo castle-bravo - 28 days ago 5x
Python Question

Generic binary operation in a class definition?

I am writing a tiny linear algebra module in Python 3, and there are a number of binary operators to define. Since each definition of a binary operator is essentially the same with only the operator itself changed, I would like to save some work by writing a generic binary operator definition only once.

For example:

class Vector(tuple):
def __new__(self, x):

# Binary operators

def __add__(self, xs):
return Vector(a + x for a, x in zip(self, xs)))
return Vector(a + x for a in self)

def __and__(self, xs):
return Vector(a & x for a, x in zip(self, xs))
return Vector(a & x for a in self)

... # mul, div, or, sub, and all other binary operations

The binary operators above all have the same form. Only the operator is changed. I wonder if I could instead write all operators at once, something like this:

def __bop__(self, xs):
bop = get_bop_somehow()
return Vector(bop(a, x) for a, x in zip(self, xs)))
return Vector(bop(a, x) for a in self)

I've heard that Python can do magical things with the
method, which I tried to use to extract the name of the operator like so:

def __getattr__(self, name):
print('Method name:', name.strip('_'))

But, unfortunately, this only works when called using the full method name, not when an operator is used. How can I write a one-size-fits-all binary operator definition?


You could use a class decorator to mutate your class and add them all in with the help of a factory function:

import operator

def natural_binary_operators(cls):
    for name, op in {
        '__add__': operator.add,
        '__sub__': operator.sub,
        '__mul__': operator.mul,
        '__truediv__': operator.truediv,
        '__floordiv__': operator.floordiv,
        '__and__': operator.and_,
        '__or__': operator.or_,
        '__xor__': operator.xor
        setattr(cls, name, cls._make_binop(op))

    return cls

class Vector(tuple):
    def _make_binop(cls, operator):
        def binop(self, other):
                return cls([operator(a, x) for a, x in zip(self, other)])
                return cls([operator(a, other) for a in self])

        return binop

There are a few other ways to do this, but the general idea is still the same.