wonderbeyond
11/14/2017 - 10:35 AM

Safe alternative of min/max in Python (the builtin min/max is evil)

Safe alternative of min/max in Python (the builtin min/max is evil)

"""
Python's builtin min function is evil

>>> min(2, 1)
1
>>> min([2, 1])
1
>>> min([2])
2
>>> min([])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: min() arg is an empty sequence
>>> min([None])
>>> min([None, 1])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: int() < NoneType()
>>> min(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not iterable
>>> min(1, None)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: NoneType() < int()
>>> 
"""


from collections import Iterable


def _min_max_ignore_none(agg, it=None, *args, **kwargs):
    """
    >>> min = min_ignore_none
    >>> max = max_ignore_none

    >>> min(2, 1), min([2, 1])
    (1, 1)
    >>> min(1, None), min(None, 1), min([1, None])
    (1, 1, 1)
    >>> min(1), min([1]), min(*[1])
    (1, 1, 1)
    >>> min(), min([]), min(*[])
    (None, None, None)
    >>> min(x for x in [])
    >>> min(None), min([None])
    (None, None)

    >>> max(1, 2), max([1, 2])
    (2, 2)
    """
    assert agg in (min, max)

    if isinstance(it, Iterable) and not args:
        it = [v for v in it if v is not None]
        if not len(it):
            return None

    if args:
        it = list(args) + [it]
        it = [v for v in it if v is not None]
        if not len(it):
            return None
    elif it:
        it = it if isinstance(it, Iterable) else [it]
    else:
        return None

    return agg(it, **kwargs)


min_ignore_none = lambda *a, **kw: _min_max_ignore_none(min, *a, **kw)
max_ignore_none = lambda *a, **kw: _min_max_ignore_none(max, *a, **kw)