6

The built-in function sum() applied to a dictionary returns the sum of its keys:

>>> sum({1: 0, 2: 10})
3

I'd like to create a subclass of the dictionary, say SubDict, and override some function to return the sum of its values, i.e.

>>> sum(SubDict((1, 0), (2, 10))
10

Which function do I need to override to achieve this functionality?

This is a general question, how to implement the built-in sum() function to a class, not just in this particular case.

mkrieger1
  • 19,194
  • 5
  • 54
  • 65
Max Li
  • 5,069
  • 3
  • 23
  • 35
  • 1
    It's worth noting that there was actually a brief discussion on python-ideas of adding a "summation protocol"—a method that classes could override to change how they're summed—as part of a larger discussion of making `sum` work better for flattening lists of lists and the list. Pretty much nobody thought it was a good idea. The original author of the `sum` builtin specifically wanted it to only work on sequences of numbers, period, and the only reason it works on any iterable over anything addable is that there was no good way to restrict it back in Python 2.0. – abarnert Aug 08 '13 at 21:57
  • thanks for this comment. I thought there would be some kind of ______sum______ method, since there's ______abs______ and the built-in abs() uses it. – Max Li Aug 08 '13 at 22:00
  • In general, only low-level functionality that couldn't be efficiently built on top of something else gets a dunder-method protocol. [Data model](http://docs.python.org/3.3/reference/datamodel.html) has a complete list of all such methods used by Python itself or its builtins; there are a few more specified by other modules like `pickle`. – abarnert Aug 08 '13 at 22:17

2 Answers2

9

sum is effectively implemented like this:

def sum(sequence, start=0):
    for value in sequence:
        start = start + value
    return start

So, you can't override sum directly… but if you can override the way for value in … works on your sequence,* or the way + works on your values, that will affect sum automatically. Of course either one will have side effects—you'll be affecting any iteration of your sequence, or any addition of your values, not just the ones inside sum.

To override iteration, you need to provide a __iter__ method that returns an appropriate iterator. To override addition, you need to provide a __add__ method.


But really, why are you trying to "override sum"? Why not just write a new function that does what you want? You can add code that special-cases your type(s) and then falls back to the builtin sum otherwise. If you want to make it more "open", you can use PEP 443 single-dispatch to make it easy to register new types to be special-cased. I think that's what you really want here.


* As agf points out in the comments, despite the parameter being called sequence, it actually takes any iterable. Which is a good thing, because dictionaries aren't sequences…

abarnert
  • 354,177
  • 51
  • 601
  • 671
  • 1
    Overriding `+` wouldn't cause a `dict` to sum by values. Also, `sum` doesn't require a sequence, it requires an iterable. You've also got `start = 0` both in the args and in the function body. – agf Aug 08 '13 at 21:35
  • @agf: But overriding `iter` _would_. And the parameters are explicitly named `sequence` and `start`. See the docstring, etc. – abarnert Aug 08 '13 at 21:37
7

You can just do:

In [1]: sum({1:0,2:10}.values())
Out[1]: 10

If you want to implement a subclass who's sum will be the sum of values, just override the __iter__ method:

In [21]: class MyDict(dict):
   ....:     def __iter__(self):
   ....:         for value in self.values():
   ....:             yield value
   ....:

In [22]: d = MyDict({1:0,2:10})

In [23]: sum(d)
Out[23]: 10

But then you wont be able to do:

for key in d:
    print d[key]

Because the __iter__ function will return values... You'll always have to use the keys() function:

for key in d.keys():
    print d[key]

Better solution would be to add a sum method:

In [24]: class MyDict(dict):
   ....:     def sum(self):
   ....:         return sum(self.values())
   ....:

In [25]: d = MyDict({1:0,2:10})

In [26]: d.sum()
Out[26]: 10
Viktor Kerkez
  • 45,070
  • 12
  • 104
  • 85
  • 4
    Doesn't answer his question of how to do this in general. – agf Aug 08 '13 at 21:36
  • thus, the summary is that it's impossible to "override" sum() of the dict, since the overriding of __iter__ destroys the dict behaviour of the subclass. Aside from having to iterate the subclass of dict the other way, through __iter__ I would also change the results of the built-in enumerate() and for sure some other functions. Did I miss anything? – Max Li Aug 08 '13 at 21:49
  • Probably :) There are many things that rely upon the fact that the dictionary will iterate through it's keys. :-/ But you can always add a `sum` method (I'll update the example). – Viktor Kerkez Aug 08 '13 at 21:52
  • @MaxLi: Well, it doesn't destroy _all_ of the dict behavior of the subclass, just the iterating-means-iterating-keys behavior (like `enumerate`, and `list`, and `max, and using it in a comprehension, and .…). If that's not what you want… then don't do it. – abarnert Aug 08 '13 at 21:53
  • Actually I find the dictionary with modified `__iter__` quite elegant! It's complementary to the normal dictionary. While most problems are perhaps better off with a normal key-iterable dictionary, others would likely benefit from a value-iterable dictionary. – sigvaldm Aug 17 '18 at 19:58