[Python-ideas] 'default' keyword argument for max(), min()

Jacob Holm jh at improva.dk
Thu Apr 16 12:33:05 CEST 2009


Jared Grubb wrote:
> On 15 Apr 2009, at 11:17, Raymond Hettinger wrote:
>> [Adam Atlas]
>>> I propose adding a "default" keyword argument to max() and min(), 
>>> which provides a value to return in the event that an empty iterable 
>>> is passed.
>>
>> Could you write your proposal out in pure python so
>> we can see how it interacts with the key-keyword
>> argument and how it works when the number of
>> positional arguments is not one.
>
> Here's one option... I'm going to cheat a little here and just wrap 
> the built-in min, but a quick/simple answer could be:
>
> def min2(*vars, **kw):
> try:
> if 'key' in kw:
> return min(*vars, key=kw['key'])
> return min(*vars)
> except Exception:
> if 'default' in kw:
> return kw['default']
> raise

This will swallow exceptions that are completely unrelated to the number 
of values we are taking the min() of. I am -1 on that. Here is my 
version. It also includes an 'initial' argument (not part of Adams 
proposal) to illustrate the difference between the two concepts. I would 
be +1 to adding 'initial' as well, but that is less interesting than 
'default' because the 'initial' behavior is easy to get in other ways, 
e.g. "min(itertools.chain((initial,), values), key=func)".

_marker = object()

def min(*args, **kwargs):
    # extract and validate kwargs
    initial = kwargs.pop('initial', _marker)
    default = kwargs.pop('default', _marker)
    key = kwargs.pop('key', _marker)
    if kwargs:
        raise TypeError('min() got an unexpected keyword argument')
    # validate args, this TypeError is needed for backwards compatibility
    if initial is _marker and default is _marker and not args:
        raise TypeError('min expected 1 arguments, got 0')
    # create iterator for the values
    if len(args) == 1:
        it = iter(args[0])
    else:
        it = iter(args)
    # extract first value if any and handle empty sequence
    if initial is not _marker:
        result = initial
    else:
        for result in it:
            break
        else:
            if default is _marker:
                raise ValueError('min() arg is an empty sequence')
            return default
    # handle remaining values
    if key is _marker:
        for value in it:
            if value < result:
                result = value
    else:
        resultkey = key(result)
        for value in it:
            valuekey = key(value)
            if valuekey < resultkey:
                result, resultkey = value, valuekey
    return result


And here is what I get with the examples so far:

>>> min()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 10, in min
TypeError: min expected 1 arguments, got 0
>>> min(default=0)
0
>>> min(1,2,default=0)
1
>>> min([1,2],default=0)
1
>>> min([1,set()])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 29, in min
TypeError: can only compare to a set
>>> min([1,set()], default=7)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 29, in min
TypeError: can only compare to a set
>>> def foo():
...     yield 1
...     raise ValueError
... 
>>> min(foo())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 28, in min
  File "<stdin>", line 3, in foo
ValueError
>>> min(foo(), default=None)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 28, in min
  File "<stdin>", line 3, in foo
ValueError



Cheers
- Jacob



More information about the Python-ideas mailing list