views:

269

answers:

8

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 ?

+7  A: 

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.

Amber
ooooh... this I didn't consider. very "perlish" approach, but clever.
Stefano Borini
I disagree about it being perl-ish. `x or y` is a very pythonic way of saying `(x ? x : y)` or `(if x then x else y)` or `(if not x then y)` or however else you want to explain it.
Mark Rushakoff
ok, just worked out of a "sing along" the "whatever or die()" typical perl phrase.
Stefano Borini
+6  A: 

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.

Ned Batchelder
+12  A: 

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.

Unknown
I was just in the process of thinking the same. I agree that it does not make sense: from the conceptual point of view, returning None w.r.t. a tuple of Nones is probably different (of course it depends on the real routine. Here we are talking mock cases)
Stefano Borini
+1 -- absolutely -- it's weird to make the function return such an whacko mix of results and have to deal with it in the caller.
Alex Martelli
@Alex: well, that depends. In general, you can have cases of routines that are supposed to return _x_ (with x = whatever) or None if not found. If you know that x is a tuple, and a tuple of two objects, it's a special case, but the case _x_ or None if not found is still valid. The fact that you unpack it is oblivious to the called routine.
Stefano Borini
But then you should treat the return value as a whole - the fact it is a tuple that you can easily unpack is just coincidental.
Roberto Liffredo
+1  A: 

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))
ars
+1  A: 
def foo(flag):
    return ((1,2) if flag else (None, None))
hughdbrown
+7  A: 

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.

Roberto Liffredo
great comment. Wish I could do +2
Stefano Borini
A: 

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.)

Brian C. Wells
Because the OP explicitly said "without having foo returning (None, None)". `return (None,)*2` is the same as `return (None,None)`.
mhawke
Are you sure that's what was meant? I thought the OP just wanted to avoid *typing* the longish expression `return None, None` all the time. My way solves that problem.
Brian C. Wells
Well, it doesn't solve it completely of course, but does reduce it, and works ever better for longer tuples of a given size.
Brian C. Wells
+1  A: 

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
Anon
standing ovation for the nice code-fu :)
Stefano Borini