A safe max() function for empty lists

43,229

Solution 1

In Python 3.4+, you can use default keyword argument:

>>> max([], default=99)
99

In lower version, you can use or:

>>> max([] or [99])
99

NOTE: The second approach does not work for all iterables. especially for iterator that yield nothing but considered truth value.

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

Solution 2

In versions of Python older than 3.4 you can use itertools.chain() to add another value to the possibly empty sequence. This will handle any empty iterable but note that it is not precisely the same as supplying the default argument as the extra value is always included:

>>> from itertools import chain
>>> max(chain([42], []))
42

But in Python 3.4, the default is ignored if the sequence isn't empty:

>>> max([3], default=42)
3

Solution 3

Another solution could be by using ternary operators:

nums = []
max_val = max(nums) if nums else 0

or

max val = max(iter(nums) if nums else [0])

Solution 4

Can create simple lambda to do this:

get_max = lambda val_list: max([ val for val in val_list if val is not None ]) if val_list else None

You can call it this way:
get_max(your_list)

Solution 5

_DEFAULT = object()

def max_default(*args, **kwargs):
    """
    Adds support for "default" keyword argument when iterable is empty.
    Works for any iterable, any default value, and any Python version (versions >= 3.4
    support "default" parameter natively).

    Default keyword used only when iterable is empty:

    >>> max_default([], default=42)
    42

    >>> max_default([3], default=42)
    3

    All original functionality is preserved:

    >>> max_default([])
    Traceback (most recent call last):
    ValueError: max() arg is an empty sequence

    >>> max_default(3, 42)
    42
    """

    default = kwargs.pop('default', _DEFAULT)
    try:
        return max(*args, **kwargs)
    except ValueError:
        if default is _DEFAULT:
            raise
        return default

Bonus:

def min_default(*args, **kwargs):
    """
    Adds support for "default" keyword argument when iterable is empty.
    Works for any iterable, any default value, and any Python version (versions >= 3.4
    support "default" parameter natively).

    Default keyword used only when iterable is empty:

    >>> min_default([], default=42)
    42

    >>> min_default([3], default=42)
    3

    All original functionality is preserved:

    >>> min_default([])
    Traceback (most recent call last):
    ValueError: min() arg is an empty sequence

    >>> min_default(3, 42)
    3
    """

    default = kwargs.pop('default', _DEFAULT)
    try:
        return min(*args, **kwargs)
    except ValueError:
        if default is _DEFAULT:
            raise
        return default
Share:
43,229

Related videos on Youtube

Alexander McFarlane
Author by

Alexander McFarlane

I'm pretty average at snowboarding Quantitative researcher designing prop trading strategies in python Random Quotes If a man does not have to work: If he does not grow crops or hunt or do something, he very quickly destroys himself - Yaka Garimala, quoting an Aboriginal saying Physics Nobel Laureate Quotes Physics is like sex: sure, it may give some practical results, but that's not why we do it - Richard Feynman All science is either physics or stamp collecting - Ernest Rutherford Anyone who has never made a mistake has never tried anything new - Albert Einstein Because of its extreme complexity, most physicists will be glad to see the end of QED - Paul Dirac

Updated on November 20, 2020

Comments

  • Alexander McFarlane
    Alexander McFarlane over 3 years

    Evaluating,

    max_val = max(a)
    

    will cause the error,

    ValueError: max() arg is an empty sequence
    

    Is there a better way of safeguarding against this error other than a try, except catch?

    a = []
    try:
        max_val = max(a)
    except ValueError:
        max_val = default 
    
  • Steven Rumbalski
    Steven Rumbalski about 8 years
    Another option for pre-3.4 would be to create a my_max function that mimics the newer behavior.
  • Alexander McFarlane
    Alexander McFarlane about 8 years
    max(iter([]) or 0) is precisely what gave me the grief in the first place... Do you have any further suggestions on what do do with it? Secondly it never occurred to me to check the docs... I assumed such a simple function was a one-in-one-out process
  • falsetru
    falsetru about 8 years
    @AlexanderMcFarlane, Duncan suggested a nice alternative. Check it out.
  • falsetru
    falsetru about 8 years
    @AlexanderMcFarlane, I'm not a native speaker. I don't understand what one-in-one-out process. Does it mean a common task?
  • Alexander McFarlane
    Alexander McFarlane about 8 years
    @falsetru by one-in-one-out I mean one argument in and one return value :)
  • Yaakov Belch
    Yaakov Belch over 7 years
    It's counter-intuitive: the max of an empty list should be infinitely small (so that max(x,list...)=max(x,max(list...))) and the min of an empty list should be infinitely large.
  • Gareth McCaughan
    Gareth McCaughan over 7 years
    Whoops, yes, I wrote that the wrong way around; my apologies. I will fix my answer and credit you for spotting the mistake. [... Done. Thanks again.]
  • MajorInc
    MajorInc over 5 years
    is there any reason to use chain over simple list concatenation e.g. max( [42], [] ) where the default is 42?
  • Duncan
    Duncan over 5 years
    @MajorInc calling max with multiple arguments has a different meaning than calling it with a single argument. With a single iterable argument max will return the largest element it contains, but with multiple arguments it compares them directly (i.e. it won't iterate). e.g. max([42], []) is a list [42] but max(chain([42], [])) and max([], default=42) both give 42.
  • Milo
    Milo over 4 years
    max_safe(42, 3, default=0) Throws TypeError: max() got an unexpected keyword argument (python 2)