Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Better "return if not None" in Python

Tags:

python

syntax

People also ask

Is it good practice to return None in Python?

Whether or not to return None explicitly is a personal decision. However, you should consider that in some cases, an explicit return None can avoid maintainability problems. This is especially true for developers who come from other programming languages that don't behave like Python does.

How do you define not None in Python?

Definition and UsageThe None keyword is used to define a null value, or no value at all. None is not the same as 0, False, or an empty string. None is a data type of its own (NoneType) and only None can be None.

Is None a substitute for Python?

Python uses the keyword None to define null objects and variables. While None does serve some of the same purposes as null in other languages, it's another beast entirely. As the null in Python, None is not defined to be 0 or any other value.

What does if None return in Python?

In Python None is a singleton. It is called the null in other languages. In your if not None: , the compiler assumes that not None means non empty, or non-zero and we know an if statement evaluates non-zero values as True and executes them.


Without knowing what else you might want to return there are a few options.

  1. You could just return the result of the function, None or not:

    return slow_function()
    

    In this, you rely on the caller knowing what to do with the None value, and really just shift where your logic will be.

  2. If you have a default value to return instead of None, you can do this:

    return slow_function() or default
    

    In this above, if slow_function is None (which is "falsy") it will return the latter value, otherwise, if slow_function returns a "truthy" value it will return that. Beware, if slow_function can return other "falsy" values, like False, [] or 0, those will be ignored.

  3. Alternatively, sometimes what you have is perfectly valid code. You want to compare against a value, and if it is value, return it. The code you have is obvious in what it does, and sometimes that is more important than the "cleverness" of the code.

As per the comments, if your code must continue running if the value is None then the most obvious way to do it is store it as a temporary value. But, thats not a bad thing, as it reads cleanly.

  • Compute a value and store it as the result
  • If there is a valid result, return it.
  • Otherwise, keep doing things to get a better result.

Better is usually very subjective, and I can't see any obvious ways to improve this from a computation perspective, and as written it is very human readable which is a clear advantage. Other solutions may be shorter or cleverer, but human readability is often an over looked advantage for code.


Your latest comment maybe makes it clearer what you want to do:

Imagine that you pass f a list and it select an item, then calls itself passing the list without the item and so on until you have no more items. The you check if the solution is feasible, if it is feasible you'll return the solution and this needs to go all the way through the call stack, otherwise you return None. In this way you'll explore all the problems in a topological order but you can also skip checks when you know that the previous chosen items won't be able to create a feasible solution.

Maybe you can try using yield instead of return. That is, your recursive function won't generate one solution, but will yield all possible solutions. Without a specific example I can't be sure what you're doing exactly, but say before it was like this:

def solve(args, result_so_far):
    if not slow_check_is_feasible(result_so_far):
        #dead-end
        return None

    if not args:
        #valid and done
        return result_so_far

    for i, item in enumerate(args):
        #pass list without args - slow
        new_args = args[:]
        del new_args[i]
        result = solve(new_args, accumulate_result(result_so_far, item)
        if result is not None:
            #found it, we are done
            return result
        #otherwise keep going

Now it looks like this:

def solve_all(args, result_so_far):
    if not slow_check_is_feasible(result_so_far):
        #dead-end
        return

    if not args:
        #yield since result was good
        yield result_so_far
        return

    for i, item in enumerate(args):
        #pass list without args - slow
        new_args = args[:]
        del new_args[i]
        for result in solve(new_args, accumulate_result(result_so_far, item):
            yield result

The benefits are:

  • You generate all answers instead of just the first one, but if you still only want one answer then you can just get the first result.
  • Before you used return values both for false checks and for answers. Now you're just only yielding when you have an answer.

Essentially you want to evaluate an expression and then use it twice without binding it to a local variable. The only way to do that, since we don't have anonymous variables, is to pass it into a function. Fortunately, the control flow for whether the current function returns isn't controlled by the functions it calls... however, exceptions do propagate up the call stack.

I wouldn't say this is better, but you could abuse exceptions to get what you want. This should never really be used and it's more an exercise in curiosity. The result would end up looking like this (note the use of the decorator):

def slow_function(x):
    if x % 5 == 0:
        return x * 200

@if_returner
def foobme(l):
    for i in l:
        print "Checking %s..." % (i,)
        return_if(slow_function(i))

print foobme([2, 3, 4, 5, 6])

Output is:

Checking 2...
Checking 3...
Checking 4...
Checking 5...
1000

The trick is to piggy-back on exception handling, since those propagate across function calls. If you like it, here's the implementation:

class ReturnExc(Exception):
    def __init__(self, val):
        self.val = val

def return_if(val):
    if val is not None:
        raise ReturnExc(val)

def if_returner(f):
    def wrapped(*args, **kwargs):
        try:
            return f(*args, **kwargs)
        except ReturnExc, e:
            return e.val
    return wrapped

For the problem where slow_function is operating over a loop, a generator expression would seem the way to go. In Python 3 everything here is lazy, so you get your filter for free:

f = filter(slow_function(x) for x in range(...))

In Python 2 you just need itertools:

from itertools import ifilter

f = ifilter(slow_function(x) for x in xrange(...))

Each iteration will only take place when you ask for it. If you need to continue the operation if the function returns false, then you need the Falseness as a sentinel, so your solution is fine:

def f():
  for x in xrange(...):
    sentinel = slow_function(x)
    if sentinel:
      return sentinel
    # continue processing

or you can save yourself a variable by using a generator here, too:

from itertools import imap

def f():
  for x in imap(slow_function, xrange(...)):
    if x:
      return x
    # continue processing

Not really a recommendation, but you could abuse a list comprehension and do something along these lines:

# Note: Doesn't work in python 3.
def func():
    if [value for value in (slow_function(),) if value is not None]:
        return value
    # continue processing...