The Built-In sum()
Function
by Christoph Schiessl on Python
The built-in sum(iterable, /, start=0)
function in Python has been around for a long time. It's designed to calculate the sum of a collection of numbers. Therefore, it takes one mandatory position-only parameter that must be an object implementing the iterable
protocol. The second parameter, start
, has a default value of 0
and is, therefore, optional.
The function sums
start
and the items of aniterable
from left to right and returns the total. Theiterable
's items are typically numbers, and the start value is not allowed to be a string.
So, here is the function in action — nothing surprising:
Python 3.12.3 (main, Apr 21 2024, 14:06:33) [GCC 13.2.1 20230801] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> sum([1, 2])
3
>>> sum([1, 2], start=3)
6
>>> sum([1.2, 2.3])
3.5
>>> sum([1.2, 2.3], start=3.1)
6.6
The parameter iterable
is required but can be empty, in which case the value of start
is returned.
Python 3.12.3 (main, Apr 21 2024, 14:06:33) [GCC 13.2.1 20230801] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> sum()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: sum() takes at least 1 positional argument (0 given)
>>> sum([])
0
>>> sum([], start=3)
3
Interestingly, the function has code specifically to reject str
objects for the start
parameter.
Python 3.12.3 (main, Apr 21 2024, 14:06:33) [GCC 13.2.1 20230801] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> sum([], start="")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: sum() can't sum strings [use ''.join(seq) instead]
However, str
objects in the iterable are allowed. The error we get is indirect because start
still defaults to the integer 0
, and the function is then trying to calculate 0 + "A"
, which gives the error you see below.
Python 3.12.3 (main, Apr 21 2024, 14:06:33) [GCC 13.2.1 20230801] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> sum(["A", "B", "C"])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'
>>> 0 + "A"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'
At this point, the article becomes more theoretical, and some of the following ideas are definitely unsuitable for real-world applications. I'm pushing sum()
to the limit because extreme cases are a great way to learn new concepts and solidify the ones you already know. That said, we can trick sum()
into concatenating strings by providing a custom object with its own implementation of def __add__(self, other)
for the start
parameter. The idea is that our custom object returns the second summand as the result. So, in code, that means custom_object + whatever is whatever
is always True
.
Python 3.12.3 (main, Apr 21 2024, 14:06:33) [GCC 13.2.1 20230801] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> class Trick:
... def __add__(self, other):
... return other
...
>>> custom_object = Trick()
>>> sum([], custom_object) is custom_object
True
>>> sum(["A"], Trick())
'A'
>>> sum(["A", "B"], Trick())
'AB'
This also demonstrates that the iterable
is traversed from left to right, as the documentation states. The summation of numbers is commutative, meaning 1 + 2 == 2 + 1
, but string concatenation is not: "A" + "B" != "B" + "A"
. The fact that the result of sum()
with ["A", "B"]
as the iterable
is "AB"
, confirms the traversal happens from left to right.
Anyway, there are also legitimate use cases where you may want to use sum()
with objects of custom classes. For instance, look at this example:
Python 3.12.3 (main, Apr 21 2024, 14:06:33) [GCC 13.2.1 20230801] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> class Currency:
... def __init__(self, value):
... self.value = value
... def __add__(self, other):
... print(f"Currency({self.value}) + Currency({other.value})")
... return Currency(self.value + other.value)
...
>>> result = sum([Currency(1), Currency(2), Currency(3)], start=Currency(4))
Currency(4) + Currency(1)
Currency(5) + Currency(2)
Currency(7) + Currency(3)
>>> print(result.value)
10
I have added the print()
statement to illustrate one last point. The traversal from left to right is potentially significant in getting the correct result (if your addition is not commutative), but it's also significant if there are side effects. In this case, the text printed to stdout
is a side effect that would be different if the direction of traversal of the iterable
differed.
Anyway, that's everything for today. I hope you found this article interesting, and I look forward to seeing you again soon!