Minimum of numbers that are not None in Python
Asked Answered
O

3

6

This question is close to mine, but not quite: List minimum in Python with None?. I need to allow for the possibility of all values in the list being None.

I have a situation where I have to look up some numbers in a database, and they may either exist or come back as null. I need to find the minimum of those existing numbers. For example:

min_if_exists(1, 2) returns 1
min_if_exists(5, None) returns 5
min_if_exists(None, None) returns None

Luckily I can do something like this:

def min_if_exists(a, b):
    return min(a, b)
elif a:
    return a
elif b:
    return b
else:
    return None

The following is the answer to the question referenced above:

lowest = min(x for x in (a, b) if x is not None)

With this, however, if a and b are None, I get an exception because I inputted an empty sequence. I could simply catch the exception, or I could include an arbitrarily large number in the sequence (a, b, 100000000). For a arbitrarily long sequence of numbers that may all be None, I could (presumably) un-Pythonically do this:

def min_if_exists(list_of_nums):
    not_a_none_exists = False
    for n in list_of_nums:
        if n is not None:
            not_a_none_exists = True
    if not_a_none_exists:
        return min(x for x in list_of_nums if x is not None)
    else:
        return None

Is there a more Pythonic, more concise way of doing this?

Orogeny answered 27/1, 2014 at 2:49 Comment(1)
You dismiss the option of catching the exception rather quickly. It seems to me that that would be a Pythonic way of doing it, considering the general philosophy of “it's better to do and beg for forgiveness than ask for permission”.Frankforter
B
7

Try this, it's a pythonic solution:

def min_if_exists(list_of_nums):
    try:
        return min(x for x in list_of_nums if x is not None)
    except ValueError:
        return None

Remember: in Python, it's easier to ask for forgiveness than permission! quoting the documentation:

This common Python coding style assumes the existence of valid keys or attributes and catches exceptions if the assumption proves false. This clean and fast style is characterized by the presence of many try and except statements. The technique contrasts with the LBYL (look before you leap) style common to many other languages such as C.

Barkley answered 27/1, 2014 at 2:53 Comment(3)
Personally I don't like this approach for two reasons: I have to remember or look up exception name (except Exception is unacceptable), and second reason is just more to type. In some cases ask for forgiveness looks cleaner, but not in this case. But this is matter of taste.Delinquent
Is there a general rule of thumb for when you should write code that will routinely raise an exception (provided that you handle it)? Like when it's the shortest solution (measured in lines of code)? I could see how catching exceptions could get out of hand in some situations, like if there are several exceptions that might be thrown. In this case, I think I'll have to agree with you that simply catching the exception is the cleanest way to go.Orogeny
@JosephKriefall I can't think of a rule of thumb for this. Catch an exception for control flow when you feel that doing it simplifies code - if we were to validate each case that might cause an exception and handle it, then the code would become unwieldy pretty soonCrimpy
A
3

If you wait for Python 3.4, you can do this quite cleanly as min and max will grow a default parameter (see issue #18111):

>>> def min_not_none(seq):
...     return min((x for x in seq if x is not None), default=None)
... 
>>> print(min_not_none([3,None,1]))
1
>>> print(min_not_none([None]))
None
Anglophile answered 27/1, 2014 at 3:49 Comment(0)
S
1

I think the exception-handling-based answer by Oscar Lopez is a very good option. Here is an alternative (probably inferior) one:

def my_min(lst):
    if lst == [None]*len(lst):
        return None
    else:
        return min(x for x in lst if x is not None)

print(my_min([0,2,-1]))           # -1
print(my_min([0,2,None]))         # 0
print(my_min([None, None, None])) # None
Spectral answered 27/1, 2014 at 3:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.