Adding two distinct subclasses as their common superclass - python

I have a class with a lot of subclasses in my code. Consider the following code:
class Dataset:
def __init__(self, samples):
self.data = samples
def __add__(self, other):
if isinstance(other, type(self)):
return type(self)(self.data + other.data)
return NotImplemented
def __radd__(self, other):
if isinstance(other, type(self)):
return type(self)(other.data + self.data)
return NotImplemented
def __str__(self):
return str(self.data)
class DatasetA(Dataset):
def __init__(self, samples):
super().__init__(samples)
self.a = len(self.data)
def __str__(self):
return f"{self.a}: {self.data}"
class DatasetB(Dataset):
def __init__(self, samples):
super().__init__(samples)
self.b = sum(self.data)
def __str__(self):
return f"{self.data} (sum: {self.b})"
d = Dataset([1, 2, 3])
a = DatasetA([4, 5])
b = DatasetB([6, 7])
print(d + d)
print(d + a)
print(a + d)
print(d + b)
print(b + d)
print(a + a)
print(b + b)
print(a + b)
print(b + a)
The idea is to define an __add__ method in the superclass that won't require being overridden in each of the subclasses and will still add them correctly (for instance, two DatasetBs should add up to another DatasetB). This works correctly (the first 7 prints are fine), however there is one additional functionality I'd like to implement, represented in the last 2 prints.
I would like any two distinct subclasses to add up to the first common superclass. For example, a + b should result in a Dataset instance. Also if we add up instances of two subclasses of DatasetB, the result should be a DatasetB instance.
I've tried changing return NotImplemented to return super().__add__(other) (and similar for __radd__), but that resulted in an AttributeError on the 3rd print statement already.
Is there a way to implement this desired functionality without breaking the existing one (i.e. still have the first 7 prints execute properly), and without explicitly having to override __add__ in each of the subclasses?

You could change:
return type(self)(self.data + other.data)
to:
klass = next(c for c in type(self).mro() if c in type(other).mro())
return klass(self.data + other.data)
This will pick the most specific superclass the two have in common, leveraging the method resolution order.

Related

Interval class - Python

I am supposed to write a class for Intervals, then I need to define addition (how to add two intervals together).
I've done this and it works:
def __add__ (self, other):
return Interval (self.a + other.a, self.b + other.b)
where a and b are ending points of one interval.
Now I need to modify the code so that addition between an interval and number c (float or int) is defined.
[a,b] + c = [a+c,b+c] and
c + [a,b] = [a+c,b+c].
I've tried a lot of things that don't work, something like:
def __add__ (self, other, *args):
if args:
return Interval (self.a + other.a, self.b + other.b)
else:
return Interval (self.a + int(number), self.b + int(number))
Whatever I try it doesn't work. If you have time, please take a look and give me a hint. I'd really appreciate that!
If you want to define both Interval(a, b) + Interval(c, d) and Interval(a, b) + c (for some non-Interval type of c), you need to examine the argument other in the definition.
def __add__(self, other):
if instanceof(other, Interval):
return Interval(self.a + other.a, self.b + other.b)
elif instanceof(other, (int, float)):
return Interval(self.a + other, self.b + other)
else:
return NotImplemented
To support c + Interval(a, b) as well, you need to define __radd__:
def __radd__(self, other):
return self + other
If you right 3 + Interval(a, b), 3.__add__(Interval(a, b)) doesn't know how to deal with an Interval, so it returns NotImplemented, which is Python's cue to try Interval(a, b).__radd__(3) instead. The definition of __radd__ usually isn't too complicated, unless your operation isn't commutative (that is, 3 + Interval(a, b) and Interval(a, b) + 3 are not equal).
You could assume that other is already an Interval and try the addition, but catch an exception when not:
def __add__ (self, other):
try:
return Interval (self.a + other.a, self.b + other.b)
except AttributeError:
pass
return Interval (self.a + int(other), self.b + int(other))
If you then want to calculate 42 + x you need the radd method:
def __radd__(self, other):
return self + other

I want to give an input of adding two numbers but it should return multiplication of those two numbers using operator overloading

I am a newbie. I want to use operator overloading which gives 3+4 but returns answer of 3*4
I have made a class and passed two functions add and mul
class A:
def __init__(self, a,b):
self.a = a
self.b = b
# adding two objects
def __add__(self, other):
return self.a + other.a , self.b + other.b
# multiply two objects
def __mul__(self, other):
return self.a * other.a , self.b +other.b
ob1 = A(1)
ob2 = A(2)
ob3 = ob1+ob2
ob4 = ob1*ob2
print(ob3)
print(ob4)
Expected: input 3 and 4 , it should show 3+4 but return 3*4
In your __mul__ and __add__ methods, you need to return an instance of A, not just some values (unless you are doing in place operations). It seems like you only want to add 2 numbers together, so maybe you should try having only 1 parameter __init__:
class A:
def __init__(self, a):
self.a = a
def __add__(self, other):
return A(self.a * other.a)
Now when you do:
A(3) + A(2)
You are getting back 2*3 as the __add__ method returns a new instance of A whose .a attribute is the product, not sum, of the given two.
You should also consider type checking or error handling as your next step. What if I typed:
A(2) + 10 # not A(10)
Would an error be raised? That’s up to you. The easiest way to cause an error to be raised if you return NotImplemented from the function. This method also allows polymorphism to take place where any object with a .a attribute will work (as long as it is something that can be multiplied).
...
def __add__(self, other):
try:
return A(self.a * other.a)
except Exception:
return NotImplemented

Equality and inheritance in python

When reading about how to implement __eq__ in python, such as in this SO question, you get recommendations like
class A(object):
def __init__(self, a, b):
self._a = a
self._b = b
def __eq__(self, other):
return (self._a, self._b) == (other._a, other._b)
Now, I'm having problems when combining this with inheritance. Specifically, if I define a new class
class B(A):
def new_method(self):
return self._a + self._b
Then I get this issue
>>> a = A(1, 2)
>>> b = B(1, 2)
>>> a == b
True
But clearly, a and b are not the (exact) same!
What is the correct way to implement __eq__ with inheritance?
If you mean that instances of different (sub)classes should not be equal, consider comparing their types as well:
def __eq__(self, other):
return (self._a, self._b, type(self)) == (other._a, other._b, type(other))
In this way A(1, 2) == B(1,2) returns False.
When performing a comparison between objects of two types where one type is derived from the other, Python ensures that the operator method is called on the derived class (in this case, B).
So to ensure that B objects compare dissimilarly to A objects, you can define __eq__ in B:
class B(A):
def __eq__(self, other):
return isinstance(other, B) and super(B, self).__eq__(other)

Cooperative multiple inheritance and python 2.7 mixins

I'm trying to use the cooperative multiple inheritance pattern to resolve a problem. A very simplified version of my python 2.7 code looks like this:
class Base1(object):
def __init__(self, a, b):
self.a = a
self.b = b
def to_tuple(self):
return self.a, self.b
def to_string(self):
return '%s.%s' % self.to_tuple() # (1)
class Base2(object):
def __init__(self, c, d):
self.c = c
self.d = d
def to_tuple(self):
return self.c, self.d
def to_string(self):
return '%s-%s' % self.to_tuple() #(2)
class MyMixin(Base1, Base2):
def __init__(self, a, b, c, d):
Base1.__init__(self, a, b)
Base2.__init__(self, c, d)
def to_tuple(self):
return Base1.to_tuple(self) + Base2.to_tuple(self)
def to_string(self):
return '{}: {} '.format(Base1.to_string(self), Base2.to_string(self))
mix = MyMixin('a', 'b', 'c', 'd')
print(mix.to_string())
After writing this code, I was expecting the result:
a.b: c-d
but the code fails. When the line #(1) is run, self is a MyMixin class, not a Base1 class, so to_tuple returns 4 items.
The only way I've found to fix this is to replace the lines #(1) and #(2) above with:
return '%s.%s' % Base1.to_tuple() # (1)
return '%s.%s' % Base2.to_tuple() # (2)
and this feels terribly wrong for a number of reasons.
What am I doing wrong?
Here is what happens. When mix.to_string() is called, first, it calls Base1.to_string(self) passing a mix instance as a self, which means when to_string is called on Base1 it has an instance of MyMixin which returns ('a','b','c','d') on to_tuple call. That's why it fails, cuz tuple contains 4 items and only 2 are required by line #1.
To solve this issue try to avoid inheritance from multiple classes with the same method signatures. Use composition instead.
class MyMixin(object):
def __init__(self, a, b, c, d):
self.base1 = Base1(a, b)
self.base2 = Base2(c, d)
def to_tuple(self):
return self.base1.to_tuple(self) + self.base2.to_tuple(self)
def to_string(self):
return '{}: {} '.format(self.base1.to_string(), self.base2.to_string())

What is the "metaclass" way to do this?

I want to write a program that accepts as input a number p and produces as output a type-constructor for a number that obeys integer arithmetic modulo p.
So far I have
def IntegersModP(p):
N = type('IntegersMod%d' % p, (), {})
def __init__(self, x): self.val = x % p
def __add__(a, b): return N(a.val + b.val)
... (more functions) ...
attrs = {'__init__': __init__, '__add__': __add__, ... }
for name, f in attrs.items():
setattr(N, name, f)
return N
This works fine, but I'd like to know what the Pythonic way to do this is, which I understand would use metaclasses.
Like this:
def IntegerModP(p): # class factory function
class IntegerModP(object):
def __init__(self, x):
self.val = x % p
def __add__(a, b):
return IntegerModP(a.val + b.val)
def __str__(self):
return str(self.val)
def __repr__(self):
return '{}({})'.format(self.__class__.__name__, self.val)
IntegerModP.__name__ = 'IntegerMod%s' % p # rename created class
return IntegerModP
IntegerMod4 = IntegerModP(4)
i = IntegerMod4(3)
j = IntegerMod4(2)
print i + j # 1
print repr(i + j) # IntegerMod4(1)
Metaclasses are for when your class needs to behave differently from a normal class or when you want to alter the behavior of the class statement. Neither of those apply here, so there's really no need to use a metaclass. In fact, you could just have one ModularInteger class with instances that record their value and modulus, but assuming you don't want to do that, it's still easy to do this with an ordinary class statement:
def integers_mod_p(p):
class IntegerModP(object):
def __init__(self, n):
self.n = n % IntegerModP.p
def typecheck(self, other):
try:
if self.p != other.p:
raise TypeError
except AttributeError:
raise TypeError
def __add__(self, other):
self.typecheck(other)
return IntegerModP(self.n + other.n)
def __sub__(self, other):
...
IntegerModP.p = p
IntegerModP.__name__ = 'IntegerMod{}'.format(p)
return IntegerModP

Categories