Returning None or a tuple and unpacking

Question:

I am always annoyed by this fact:

$ cat foo.py
def foo(flag):
    if flag:
        return (1,2)
    else:
        return None

first, second = foo(True)
first, second = foo(False)

$ python foo.py
Traceback (most recent call last):
  File "foo.py", line 8, in <module>
    first, second = foo(False)
TypeError: 'NoneType' object is not iterable

The fact is that in order to correctly unpack without troubles I have either to catch the TypeError or to have something like

values = foo(False)
if values is not None:
    first, second = values

Which is kind of annoying. Is there a trick to improve this situation (e.g. to so set both first and second to None without having foo returning (None, None)) or a suggestion about the best design strategy for cases like the one I present ? *variables maybe ?

Asked By: Stefano Borini

||

Answers:

Well, you could do…

first,second = foo(True) or (None,None)
first,second = foo(False) or (None,None)

but as far as I know there’s no simpler way to expand None to fill in the entirety of a tuple.

Answered By: Amber

I don’t think there’s a trick. You can simplify your calling code to:

values = foo(False)
if values:
    first, second = values

or even:

values = foo(False)
first, second = values or (first_default, second_default)

where first_default and second_default are values you’d give to first and second as defaults.

Answered By: Ned Batchelder

I don’t see what is wrong with returning (None,None). It is much cleaner than the solutions suggested here which involve far more changes in your code.

It also doesn’t make sense that you want None to automagically be split into 2 variables.

Answered By: Unknown

You should be careful with the x or y style of solution. They work, but they’re a bit broader than your original specification. Essentially, what if foo(True) returns an empty tuple ()? As long as you know that it’s OK to treat that as (None, None), you’re good with the solutions provided.

If this were a common scenario, I’d probably write a utility function like:

# needs a better name! :)
def to_tup(t):
    return t if t is not None else (None, None)

first, second = to_tup(foo(True))
first, second = to_tup(foo(False))
Answered By: ars
def foo(flag):
    return ((1,2) if flag else (None, None))
Answered By: hughdbrown

I think there is a problem of abstraction.

A function should maintain some level of abstraction, that helps in reducing complexity of the code.
In this case, either the function is not maintaining the right abstraction, either the caller is not respecting it.

The function could have been something like get_point2d(); in this case, the level of the abstraction is on the tuple, and therefore returning None would be a good way to signal some particular case (e.g. non-existing entity). The error in this case would be to expect two items, while actually the only thing you know is that the function returns one object (with information related to a 2d point).

But it could also have been something like get_two_values_from_db(); in this case the abstraction would be broken by returning None, because the function (as the name suggest) should return two values and not one!

Either way, the main goal of using a function – reducing complexity – is, at least partially, lost.

Note that this issue would not appear clearly with the original name; that’s also why it is always important to give good names to function and methods.

Answered By: rob

How about this:

$ cat foo.py 
def foo(flag):
    if flag:
        return (1,2)
    else:
        return (None,)*2

first, second = foo(True)
first, second = foo(False)

Edit: Just to be clear, the only change is to replace return None with return (None,)*2. I am extremely surprised that no one else has thought of this. (Or if they have, I would like to know why they didn’t use it.)

Answered By: Brian C. Wells

OK, I would just return (None, None), but as long as we are in whacko-land (heh), here is a way using a subclass of tuple. In the else case, you don’t return None, but instead return an empty container, which seems to be in the spirit of things. The container’s “iterator” unpacks None values when empty. Demonstrates the iterator protocol anyway…

Tested using v2.5.2:

class Tuple(tuple):
    def __iter__(self):
        if self:
            # If Tuple has contents, return normal tuple iterator...
            return super(Tuple, self).__iter__()
        else:
            # Else return a bogus iterator that returns None twice...
            class Nonerizer(object):
                def __init__(self):
                    self.x=0
                def __iter__(self):
                    return self
                def next(self):
                    if self.x < 2:
                        self.x += 1
                        return None
                    else:
                        raise StopIteration
            return Nonerizer()


def foo(flag):
    if flag:
        return Tuple((1,2))
    else:
        return Tuple()  # It's not None, but it's an empty container.

first, second = foo(True)
print first, second
first, second = foo(False)
print first, second

Output is the desired:

1 2
None None
Answered By: Anon

I found a solution for this problem:

Return None or return an object.

However you don’t want to have to write a class just to return an object. For this, you can use a named tuple

Like this:

from collections import namedtuple
def foo(flag):
if flag:
   return None
else:
    MyResult = namedtuple('MyResult',['a','b','c']
    return MyResult._make([1,2,3])

And then:

result = foo(True) # result = True
result = foo(False) # result = MyResult(a=1, b=2, c=3)

And you have access to the results like this:

print result.a # 1
print result.b # 2
print result.c # 3
Answered By: Diego

Over 10 years later, if you want to use default values I don’t think there is a better way than the one already provided:

first, second = foo(False) or (first_default, second_default)

However, if you want to skip the case when None is returned, starting from Python 3.8 you can use the walrus operator (ie. assignment expressions) – also note the simplified foo:

def foo(flag):
    return (1, 2) if flag else None

if values := Foo(False):
    (first, second) = values

You could use an else branch to assign default values that’s worse than the previous or option.

Sadly, the walrus operator does not support unparenthesized tuples so it is just a one line gain compared to:

values = foo(False)
if values:
    first, second = values
Answered By: jnovo

One mechanism you can use to avoid the problem entirely when you have control of the method foo is to change the prototype to allow giving a default. This works if you are wrapping state but can’t guarantee that a particular tuple value exists.

# self.r is for example, a redis cache

# This method is like foo - 
# it has trouble when you unpack a json serialized tuple
def __getitem__(self, key):
    val = self.r.get(key)
    if val is None:
        return None
    return json.loads(val)

# But this method allows the caller to 
# specify their own default value whether it be
# (None, None) or an entire object
def get(self, key, default):
    val = self.r.get(key)
    if val is None:
        return default
    return json.loads(val)
Answered By: dhakim
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.