The Strategy pattern is your friend here. I'll also touch on a couple of other ways you can clean up code.
You can read about the strategy pattern here: https://en.wikipedia.org/wiki/Strategy_pattern
You said "As you can see, every overloaded method is a copy/paste of the previous with just small changes." This is your hint to use this pattern. If you can make the small change into a function, then you can write the boilerplate code once and focus on the interesting parts.
class Vector:
def _arithmitize(self, other, f, error_msg):
if isinstance(other, int) or isinstance(other, float):
tmp = list()
for a in self.l:
tmp.append(func(a, other))
return Vector(tmp)
raise ValueError(error_msg)
def _err_msg(self, op_name):
return "We can only {} a vector by a scalar".format(opp_name)
def __mul__(self, other):
return self._arithmitize(
other,
lambda a, b: a * b,
self._err_msg('mul'))
def __div__(self, other):
return self._arithmitize(
other,
lambda a, b: a / b,
self._err_msg('div'))
# and so on ...
We can clean this up a little more with a list comprehension
class Vector:
def _arithmetize(self, other, f, error_msg):
if isinstance(other, int) or isinstance(other, float):
return Vector([f(a, other) for a in self.l])
raise ValueError(error_msg)
def _err_msg(self, op_name):
return "We can only {} a vector by a scalar".format(opp_name)
def __mul__(self, other):
return self._arithmetize(
other,
lambda a, b: a * b,
self._err_msg('mul'))
def __div__(self, other):
return self._arithmetize(
other,
lambda a, b: a / b,
self._err_msg('div'))
We can improve the type check
import numbers
class Vector:
def _arithmetize(self, other, f, error_msg):
if isinstance(other, number.Numbers):
return Vector([f(a, other) for a in self.l])
raise ValueError(error_msg)
We can use operators instead of writing lambdas:
import operators as op
class Vector:
# snip ...
def __mul__(self, other):
return self._arithmetize(other, op.mul, self._err_msg('mul'))
So we end up with something like this:
import numbers
import operators as op
class Vector(object):
def _arithmetize(self, other, f, err_msg):
if isinstance(other, numbers.Number):
return Vector([f(a, other) for a in self.l])
raise ValueError(self._error_msg(err_msg))
def _error_msg(self, msg):
return "We can only {} a vector by a scalar".format(opp_name)
def __mul__(self, other):
return self._arithmetize(op.mul, other, 'mul')
def __truediv__(self, other):
return self._arithmetize(op.truediv, other, 'truediv')
def __floordiv__(self, other):
return self._arithmetize(op.floordiv, other, 'floordiv')
def __mod__(self, other):
return self._arithmetize(op.mod, other, 'mod')
def __pow__(self, other):
return self._arithmetize(op.pow, other, 'pow')
There are other ways you might dynamically generate these, but for a small set of functions like this, readability counts.
If you need to generate these dynamically, try something like this:
class Vector(object):
def _arithmetize(....):
# you've seen this already
def __getattr__(self, name):
funcs = {
'__mul__': op.mul, # note: this may not actually work with dunder methods. YMMV
'__mod__': op.mod,
...
}
def g(self, other):
try:
return self._arithmetize(funcs[name],...)
except:
raise NotImplementedError(...)
return g
If you find this dynamic example does not work, check out make operators overloading less redundant in python?, which handles the case of dynamically creating dunder_methods in most python implementations.
return Vector(i*other for i in self.l)is much more compact than your four lines. As forisinstance, why not let Python itself raiseTypeError?import numpyNotImplementedinstead of raising the error, check out here