1

Is there a more pythonic way to implement elementwise addition for named tuples?

Using this class that inherits from a namedtuple generated class named "Point I can do elementwise addition for this specific named tuple.

    class Point(namedtuple("Point", "x y")):
    def __add__(self, other):
        return Point(x = self.x + other.x, y = self.y + other.y)

If we use this functionality:

print(Point(x = 1, y = 2) + Point(x = 3, y = 1))

The result is:

Point(x=4, y=3)

Is there a more pythonic way to do this in general? Is there a generalized way to do this that can extend elementwise addition to all namedtuple generated objects?

Isaacnfairplay
  • 217
  • 2
  • 18
  • There's plenty wrong with the current implementation. Mainly, as the OP says, it's not general. You can just iterate over named tuples like any other tuples. So that's what I would use to answer this. No need though because the answer that popped up while I wrote this uses exactly that. – Joooeey Jul 07 '22 at 13:11
  • But you're right @DanielHao , OP's implementation also has advantages in certain cases. E.g., when you expect to work with duck types that support `.x` and `.y` but not iteration. More detailed info on the use case from the OP would indeed help if the current answer isn't good enough. – Joooeey Jul 07 '22 at 13:21
  • Thanks. Guess I just missed the *points*. Time to grab a cup of *coffee* now ... ;-) ^^^ I did learn what you've described here. TIL. – Daniel Hao Jul 07 '22 at 13:21

2 Answers2

2

(Named)tuples are iterable, so you could use map and operator.add. Whether this is an improvement is debatable. For 2D points, almost certainly not, but for higher-dimensional points, it would be.

from operator import add


class Point(namedtuple("Point", "x y")):

    def __add__(self, other):
        return Point(*map(add, self, other))
chepner
  • 497,756
  • 71
  • 530
  • 681
  • Love your solution, I've learnt something new!! :) – Riccardo Bucco Jul 07 '22 at 13:13
  • 2
    I think this solution also allows stuff like. `Point(2, 5) + [0, 1] = Point(2, 6)`. You may or may not want to guard against this. – Joooeey Jul 07 '22 at 13:13
  • 1
    Indeed; you could accept that `Point`s can be added to arbitrary iterables, or provide a runtime check, or type-hint `__add__` so that `other` "must" be a `Point` object. – chepner Jul 07 '22 at 13:16
2

This is a possible solution:

class Point(namedtuple("Point", "x y")):
    def __add__(self, other):
        return Point(**{field: getattr(self, field) + getattr(other, field)
                        for field in self._fields})
Riccardo Bucco
  • 13,980
  • 4
  • 22
  • 50
  • 1
    The duct typing with getattr is very handy. I can add this to a regular named tuple with the same or more fields without any issue and it does not allow me to add to those with fewer fields, avoiding missing values in the namedtuple. While this does not answer my second question about extending the namedtuple factory to implement elementwise addition for all generated classes, it is good enough for my purposes. – Isaacnfairplay Jul 07 '22 at 14:41