make operators overloading less redundant in python?

I’m writing a class overloading the list type.
I just wrote this and I’m wondering if there exists any other way less redundant to do it :

class Vector:
def __mul__(self, other):
    #Vector([1, 2, 3]) * 5 => Vector([5, 10, 15])
    if isinstance(other, int) or isinstance(other, float):
        tmp = list()
        for i in self.l:
            tmp.append(i * other)
        return Vector(tmp)
    raise VectorException("We can only mul a Vector by a scalar")

def __truediv__(self, other):
    #Vector([1, 2, 3]) / 5 => Vector([0.2, 0.4, 0.6])
    if isinstance(other, int) or isinstance(other, float):
        tmp = list()
        for i in self.l:
            tmp.append(i / other)
        return Vector(tmp)
    raise VectorException("We can only div a Vector by a Scalar")

def __floordiv__(self, other):
    #Vector([1, 2, 3]) // 2 => Vector([0, 1, 1])
    if isinstance(other, int) or isinstance(other, float):
        tmp = list()
        for i in self.l:
            tmp.append(i // other)
        return Vector(tmp)
    raise VectorException("We can only div a Vector by a Scalar")

As you can see, every overloaded method is a copy/paste of the previous with just small changes.

Solution:

What you want to do here is dynamically generate the methods. There are multiple ways to do this, from going super-dynamic and creating them on the fly in a metaclass’s __getattribute__ (although that doesn’t work for some special methods—see the docs)
to generating source text to save in a .py file that you can then import. But the simplest solution is to create them in the class definition, something like this:

def _make_op_method(op):
    def _op(self, other):
        if isinstance(other, int) or isinstance(other, float):
            tmp = list()
            for i in self.l:
                tmp.append(op(i. other))
            return Vector(tmp)
        raise VectorException("We can only {} a Vector by a scalar".format(
            op.__name__.strip('_'))
    _op.__name__ = op.__name__
    return _op

__mul__ = _make_op(operator.__mul__)
__truediv__ = _make_op(operator.__truediv__)
# and so on

You can get fancier and set _op.__doc__ to an appropriate docstring that you generate (see functools.wraps in the stdlib for some relevant code), and build __rmul__ and __imul__ the same way you build __mul__, and so on. And you can write a metaclass, class decorator, or function generator that wraps up some of the details if you’re going to be doing many variations of the same thing. But this is the basic idea.

The operator.mul, etc., come from the operator module in the stdlib—they’re just trivial functions where operator.__mul__(x, y) basically just calls x * y, and so on, made for when you need to pass around an operator expression as a function.

There are some examples of this kind of code in the stdlib—although far more examples of the related but much simpler __rmul__ = __mul__.

The key here is that there’s no difference between names you create with def and names you create by assigning with =. Either way, __mul__ becomes an attribute of the class, and its value is a function that does what you want.

If you don’t understand how that works, you probably shouldn’t be doing this, and should settle for Ramazan Polat’s answer. It’s not quite as compact, or as efficient, but it’s surely easier to understand.

Python overloading non-existent operator works, why?

While messing around with overloading operators and namedtuples, I’ve stumbled on some weird behavior which works, for some reason or another:

https://repl.it/repls/RemorsefulFlawlessAfricanwildcat

import collections, math

Point = collections.namedtuple("Point", ["x", "y"])
Point.__floor__ = lambda self: Point(int(math.floor(self.x)), int(math.floor(self.y)))
print(math.floor(Point(1.4, -5.9)))
#prints: Point(x=1, y=-6)

Does anyone have any insight into this? Why does it work?
If I remove the Point.__floor__ line, it doesn’t work.


Did the math package define a __floor__ operator somewhere?
OR
Does Python parse Point.__XXX__ to extract XXX and compare with the name of the thing (function/operator) that acts on the argument?

I’m confused, probably because I don’t know how exactly these things work deep down.

Solution:

From the docs (emphasis mine):

math.floor(x)

Return the floor of x, the largest integer less than or equal to x. If x is not a float, delegates to x.__floor__(), which should return an Integral value.

Some questions about __getattr__ and __getattribute__?

The first demo:

class B:
    def __init__(self):
        self.name = '234'
    # def __getattribute__(self, name):
    #     print('getattr')
    def __getattr__(self, name):    
        print('get')
    def __setattr__(self, name, value):
        print('set')
    def __delattr__(self, name):
        print('del')


b = B()
print(b.__dict__)
b.name

b.__dict__ is {}, but the second demo:

class B:
    def __init__(self):
        self.name = '234'
    def __getattribute__(self, name):
        print('getattr')
    def __getattr__(self, name):
        print('get')
    def __setattr__(self, name, value):
        print('set')
    def __delattr__(self, name):
        print('del')


b = B()
print(b.__dict__)
b.name

b.__dict__ is None, why? And b.__dict__ invokes __getattribute__, but don’t invoke __getattr__, does it mean __getattribute__ will prevent from invoking __getattr__?

Solution:

The __getattribute__, __setattr__ and __delattr__ methods are called for all attribute access (getting, setting and deleting). __getattr__ on the other hand is only called for missing attributes; it is not normally already implemented, but if it is then __getattribute__ calls it if it could not otherwise locate the attribute, or if an AttributeError was raised by __getattribute__.

You replaced the standard implementations of the 3 main methods with methods that do nothing but print and return None (the default in the absence of an explicit return statement). __dict__ is just another attribute access, and your __getattribute__ method returns None, and never itself calls __getattr__ or raises an AttributeError.

From the Customizing attribute access documentation:

object.__getattr__(self, name)
Called when an attribute lookup has not found the attribute in the usual places (i.e. it is not an instance attribute nor is it found in the class tree for self).

and

object.__getattribute__(self, name)
Called unconditionally to implement attribute accesses for instances of the class. If the class also defines __getattr__(), the latter will not be called unless __getattribute__() either calls it explicitly or raises an AttributeError.

(Bold emphasis mine).

Either call the base implementation (via super().__getattribute__) or raise an AttributeError:

>>> class B:
...     def __init__(self):
...         self.name = '234'
...     def __getattribute__(self, name):
...         print('getattr')
...         return super().__getattribute__(name)
...     def __getattr__(self, name):
...         print('get')
...     def __setattr__(self, name, value):
...         print('set')
...     def __delattr__(self, name):
...         print('del')
...
>>> b = B()
set
>>> b.__dict__
getattr
{}
>>> b.name
getattr
get
>>> class B:
...     def __init__(self):
...         self.name = '234'
...     def __getattribute__(self, name):
...         print('getattr')
...         raise AttributeError(name)
...     def __getattr__(self, name):
...         print('get')
...     def __setattr__(self, name, value):
...         print('set')
...     def __delattr__(self, name):
...         print('del')
...
>>> b = B()
set
>>> b.__dict__
getattr
get
>>> b.name
getattr
get

Note that by calling super().__getattribute__ the actual __dict__ attribute is found. By raising an AttributeError instead, __getattr__ was called, which also returned None.