14

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?

1
  • Which is more pythonic - returning None or returning a tuple of Nones? Commented Jul 5, 2018 at 4:53

6 Answers 6

12

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.

Sign up to request clarification or add additional context in comments.

1 Comment

That is nice, but I am always concerned about the overhead of raising and handling exceptions
10

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.

5 Comments

I don't see how this is better than returning a tuple of Nones.
If it is better, it's probably because it doesn't require changing the contents of the function.
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: Yes, I agree with that, but the question was explicitly looking for other solutions.
The problem with this construct is that if I have multiple function calls, I would have to repeat this construct, which is not nice to read more than once....
8

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.

6 Comments

Heh. So you are. But I subclassed ArgumentError!
Yeah, that might be a better option :)
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.
That's true; it's something of a semantic argument. It'll depend on the actual content of the code.
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 ?
|
4

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.

3 Comments

I agree. In fact that way it maintains the return signature of function. +1
Maintaining return signatures is soooo static ;)
@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.
2

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.

Comments

1

This is similar to the previous answer. You could return either an object instance or None

def MyFunc(a):
    class MyFuncClass(object):
        def __init__(self, **kwargs):
            self.__dict__.update(kwargs)
    if a < 0:
        return None
    return MyFuncClass(one=a+1, two=a+2, three=a+3)

o = MyFunc(1)
if o is not None:
    print o.one, o.two, o.three

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.