Why don't `max` and `min` return the appropriate infinities when called with empty iterables?

Viewed 85

I was writing up a blog article on what reduce is, how it works, and the functions it hides behind, and at a certain point I talk about sum, all, any, max, and min, all reductions.

Then, I talk about how reduce accepts the third argument, the initial value, that should be the identity element of the function you are using. Then, I proceed to show some specialised reductions have the identity element baked in:

>>> sum([])
0
>>> import math
>>> math.prod([])
1
>>> all([])
True

So, why is it that max and min throw errors when called with empty iterables, when their operations have identity elements?

>>> max([])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: max() arg is an empty sequence

We can do float("-inf"), so why isn't this the value returned by max([])? Similarly, why doesn't min([]) return float("inf")?

1 Answers

Because min() and max() accept any type, and they might not be comparable with floats. For example, strings:

>>> min('a', 'b')
'a'
>>> min(float('-inf'), 'a', 'b')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: '<' not supported between instances of 'str' and 'float'

If you want the behaviour you're talking about, you can use the default parameter:

>>> min([], default=float('-inf'))
-inf

So that is to say, min() and max() have broader applications than the other functions you mentioned. sum() and math.prod() are primarily used for numbers, so it makes sense to give them numeric start values. Although, you can change that with their start parameters. Here are some examples with lists, though they're not very idiomatic.

>>> math.prod([2, 3], start=['a'])  # better: ['a'] * math.prod([2, 3])
['a', 'a', 'a', 'a', 'a', 'a']
>>> sum([['a'], ['b']], start=[])  # better: list(itertools.chain(['a'], ['b']))
['a', 'b']
Related