Implementing addition for a custom class

Question:

I’ve got a class, A, which carries some data in dictionaries.

class A:
    def __init__(self, names: set) -> None:
        self.d = {name : list() for name in names}

I am looking to define addition for my class, for which I want to return another instance of A with the combined contents of the dictionaries of the two instances of A.

I get that I need define the __add__ method, but since I only want the addition to be defined for the A type, I first want to check that they match types. Ideally I’d want to do this with a `try except for readability, but I’ve heard that I’m supposed to use an ordinary if statement. I’ve also got some issues with the TypeError I’m raising, it’s coming out weird.

    def __add__(self, a2):
        if type(self) == type(a2):
            # add functionality later
            pass 
        else:
            raise TypeError

This is what the addition looks like when it isn’t working:

A({'a'}) + 2
Traceback (most recent call last):
  File "<pyshell#20>", line 1, in <module>
    A({'a'}) + 2
  File "C:...test.py", line 20, in __add__
    raise TypeError
TypeError

My question is:

Can/should this be done with a try except instead, and should I not be raising a TypeError?

Asked By: LegendWK

||

Answers:

You should raise an instance of TypeError with a meaningful description of the error. You can then catch that and emit the description.

You probably want something like this:

class A:
    def __init__(self, names: set) -> None:
        self.d = {name : [] for name in names}
    def __add__(self, n):
        if isinstance(n, type(self)):
            # do something with n and return a new instance of A
            return A(set(self.d))
        else:
            raise TypeError(f'Invalid parameter for add [{type(n)}]')

try:
    a = A({'a'}) + 'b'
except TypeError as e:
    print(e)

Output:

Invalid parameter for add [<class 'str'>]
Answered By: OldBill

I’m not sure to say this should always be implemented this way, But I usually do this:

  1. Instead of type, I use isintance() to cover the inheritance. The instances of the subclass of A, are A.
  2. I return NotImplemented object so that maybe the other object had implemented __radd__. If the other object didn’t implement __radd__, you will get a meaningful TypeError message. Python does that for you. Returning NotImplemented object is the recommended approach:

Numeric methods and rich comparison methods should return this value
if they do not implement the operation for the operands provided.

class A:
    def __init__(self, names: set) -> None:
        self.d = names

    def __add__(self, other):
        if not isinstance(other, A):
            return NotImplemented
        return A(self.d | other.d)


class B:
    def __radd__(self, other):
        print("radd called")

class C:
    pass


obj1 = A({1})
obj2 = A({2})
obj3 = B()
obj4 = C()

print(obj1 + obj2) # calls A.__add__
print(obj1 + obj3) # calls B.__radd__
print(obj1 + obj4) # TypeError: unsupported operand type(s) for +: 'A' and 'C'
Answered By: S.B
Categories: questions Tags:
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.