Безопасная функция max() для пустых списков

Оценка,

max_val = max(a)

приведет к ошибке,

ValueError: max() arg is an empty sequence

Существует ли лучший способ защиты от этой ошибки, отличной от try, except catch?

a = []
try:
    max_val = max(a)
except ValueError:
    max_val = default 

Ответы

Ответ 1

В Python 3.4+ вы можете использовать default аргумент ключевого слова:

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

В более низкой версии вы можете использовать or:

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

ПРИМЕЧАНИЕ. Второй подход не работает для всех итераций. особенно для итератора, которые не дают ничего, кроме значения истины.

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

Ответ 2

В версиях Python старше 3.4 вы можете использовать itertools.chain(), чтобы добавить другое значение в возможную пустую последовательность. Это будет обрабатывать любые пустые итерации, но обратите внимание, что это не то же самое, что и поставлять аргумент default, поскольку добавочное значение всегда включено:

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

Но в Python 3.4 по умолчанию игнорируется, если последовательность не пуста:

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

Ответ 3

Другим решением может быть использование троичных операторов:

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

или

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

Ответ 4

_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

Бонус:

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

Ответ 5

Максимум пустой последовательности "должен" быть бесконечно малой величиной любого типа, который имеют элементы последовательности. К сожалению, (1) с пустой последовательностью вы не можете определить, какой тип должны были иметь элементы, и (2) есть, например, не такая вещь, как наибольшее отрицательное целое в Python.

Итак, вам нужно помочь max, если вы хотите, чтобы в этом случае было что-то разумное. В последних версиях Python есть аргумент default для max (который мне кажется вводящим в заблуждение именем, но неважно), который будет использоваться, если вы пройдете в пустой последовательности. В более старых версиях вам просто нужно убедиться, что последовательность, в которой вы проходите, не пуста - например, or с помощью одноэлементной последовательности, содержащей значение, которое вы хотели бы использовать в этом случае.

[EDITED после публикации, потому что Яков Белх любезно отметил в комментариях, что я написал "бесконечно большой", где я должен был написать "бесконечно мало".]

Ответ 6

Учитывая все комментарии выше, это может быть оболочка, подобная этой:

def max_safe(*args, **kwargs):
    """
    Returns max element of an iterable.

    Adds a `default` keyword for any version of python that do not support it
    """
    if sys.version_info < (3, 4):  # `default` supported since 3.4
        if len(args) == 1:
            arg = args[0]
            if 'default' in kwargs:
                default = kwargs.pop('default')
                if not arg:
                    return default

                # https://stackoverflow.com/info/36157995#comment59954203_36158079
                arg = list(arg)
                if not arg:
                    return default

                # if the `arg` was an iterator, it exhausted already
                # so use a new list instead
                return max(arg, **kwargs)

    return max(*args, **kwargs)