views:

85

answers:

5

Hello,

Suppose the following:

def MyFunc(a):
  if a < 0:
    return None
  return (a+1, a+2, a+3)

v1, v2, v3 = MyFunc()
# Bad ofcourse, if the result was None

What is the best way to define a function that returns a tuple and yet can be nicely called. Currently, I could do this:


r = MyFunc()
if r:
  v1, v2, v3 = r
else:
  # bad!!
  pass

What I don't like about this is that I have to use a single variable and then unpack it.

Another solution is I could have the function return a tuple full of Nones so that the caller can nicely unpack....

Anyone can suggest a better design?

+3  A: 

This should work nicely:

v1, v2, v3 = MyFunc() or (None, None, None)

When MyFunc() returns a tuple, it will be unpacked, otherwise it will be substituted for a 3-tuple of None.

recursive
I don't see how this is better than returning a tuple of `None`s.
Skilldrick
@Skilldrick: It's pretty!
katrielalex
If it is better, it's probably because it doesn't require changing the contents of the function.
recursive
(Just to clarify, I didn't downvote you)
Skilldrick
recursive: while the syntax looks nice, it is not that efficient if you need to call MyFunc() many times.As suggested, better returning the tuple of Nones
lallous
@lallous: Yes, I agree with that, but the question was explicitly looking for other solutions.
recursive
+9  A: 

How about raise an ArgumentError? Then you could try calling it, and deal with the exception if the argument is wrong.

So, something like:

try:
    v1, v2, v3 = MyFunc()
except ArgumentError:
    #deal with it

Also, see katrielalex's answer for using a subclass of ArgumentError.

Skilldrick
+1 Beat me to the punch!
katrielalex
+6  A: 

recursive has a truly elegant and Pythonic solution. BUT: why do you want to return None? Python has a way of handling errors, and that is by raising an exception:

class AIsTooSmallError( ArgumentError ): pass

and then

raise AIsTooSmallError( "a must be positive." )

The reason this is better is that returning a value indicates that you have completed processing and are passing back your answer. This is fine if you have done some processing, but it's silly if you are immediately returning None.

katrielalex
Way ahead of ya :) But great minds...
Skilldrick
+1: don't return None to indicate error.
nosklo
Heh. So you are. But I subclassed `ArgumentError`!
katrielalex
Yeah, that might be a better option :)
Skilldrick
You could argue that it's not an error condition that causes `None` to be returned. It could just be that the function doesn't have an answer.
Gabe
That's true; it's something of a semantic argument. It'll depend on the actual content of the code.
katrielalex
Now that deserves another question. I haven't fully researched yet but:What is the cost of Python exceptions?Or rephrased differently, what is better:- returning (None, None, None)or- throwing an exception?
lallous
It depends. Python exceptions aren't *very* efficient, but you're not using them in a tight loop so that's OK.
katrielalex
+2  A: 

Another solution is I could have the function return a tuple full of Nones so that the caller can nicely unpack....

What's wrong with that? Consistency is a good thing.

S.Lott
I agree. In fact that way it maintains the return signature of function. +1
simplyharsh
Maintaining return signatures is soooo static ;)
Skilldrick
@Skilldrick: In my experience, a consistent return signature promotes dynamism by allowing one to remain ignorant of silly details and exceptions. But I'm just lazy.
S.Lott
+1  A: 

If you want the v1, v2, v3 objects to exist and be set to a default value in the case of an error, return the default values yourself. This will make the calling code simpler by not relying on the caller to set them manually:

def MyFunc(a):
    if a < 0:
        # can't use a negative value; just return some defaults
        return (None, None, None)
    return (a+1, a+2, a+3)

On the other hand, if a default return is not appropriate and a negative argument is considered a serious error, raise an exception:

def MyFunc(a):
    if a < 0:
        # sorry, negative values are unacceptable
        raise ValueError('cannot accept a negative value')
    return (a+1, a+2, a+3)

On the third hard, returning None may be preferable sometimes when returning a single object, as is the case with the search() and match() functions of the re module. It somehow stands between the first two cases, because matching failure is an expected outcome, while a default return object wouldn't be very useful anyway.

efotinis