1

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?

2
  • 1
    You'd probably want to raise an actual instance of a type error, say raise TypeError(f"{self} can't be added to {a2}")... Commented Oct 4, 2022 at 7:43
  • 1
    And using a try/except is really kind of up to you and what the class actually does. If you don't have a strict type guard (like you do now), your type will happily work with dict-esque types too, which could well be what you want. Commented Oct 4, 2022 at 7:44

2 Answers 2

3

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'
Sign up to request clarification or add additional context in comments.

1 Comment

Actually even the docs for Python state you should return NotImplemented.
2

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'>]

2 Comments

I don't think return self is actually desired here. __iadd__ is supposed to be in-place operation, __add__ is not.
@matszwecja Thank you. I was thinking of iadd. Code corrected (I think)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.