Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle functions return value in Python

Tags:

python

The function multiply_by_ten takes a numeric argument, multiplies it by ten and returns the result back. Before this function performs its multiplication it checks if the argument is a numeric. If the argument is not numeric, the function prints out the message notifying that the argument is not a digit and returns None.

Question. Some developers believe that any given function should be returning the same type of value regardless of the circumstances. So, if I would follow a such opinion then this function should not be returning None. How to handle a situation like this? Should the argument be checked before it is being sent to a function? Why?

 def multiply_by_ten(arg):
    if not str(arg).isdigit():
        print 'arg is not a digit'
        return
    return float(arg) * 10


result = multiply_by_ten('abc')
like image 819
alphanumeric Avatar asked Jul 15 '17 04:07

alphanumeric


2 Answers

If an author believes a function should only return only one type (None really isn't a return value, it's the absence of one in most cases), the correct answer in this case would be to throw an exception. The correct logic here, IMO, using the EAFP principle is:

 def multiply_by_ten(arg):
    return float(arg) * 10


try:
    result = multiply_by_ten('abc')
except ValueError:
    pass

I also really don't recommend repeating what the standard library already does for you, since your own implementation is typically worse than what is already done for you. For example:

>>> "0.01e-6".isdigit()
False
>>> float("0.01e-6")
1e-8

If the function already checks the validity of the arguments passed too it, and throws an exception on failure, you don't need to double-check it.

Finally, I think the idea that a function should return a single type is dangerous: exception handling is great, so is returning invalid flags, but Python is a dynamic language for a reason: it allows polymorphism by design. Use it within reason.

Finally, a little perspective. In C++, a statically typed language, there exist probably a thousand different implementations of any, optional, union, and variant, all which aim to hold multiple types in the same container. Even in statically-typed languages, polymorphism is useful. If used sparing, polymorphism is a useful feature of a language.

like image 199
Alexander Huszagh Avatar answered Sep 25 '22 15:09

Alexander Huszagh


I have a number of problems with this function as written.

  1. It does two very different things: it either does some math and returns a useful answer, or it prints a message. That throws up some red flags already.

  2. It prints an error to stdout. Utility code should avoid printing if possible anyway, but it should never complain to stdout. That belongs to the application, not to spurious errors.

    If a utility function needs to complain and there's no other way to do it, use stderr as a last resort. (But in Python, you have exceptions and warnings, so there's definitely another way to do it.) Print-debugging is fine, of course — just make sure you delete it when you're done. :)

  3. If something goes wrong, the caller doesn't know what. It gets a None back, but that doesn't really explain the problem; the explanation goes to a human who can't do anything about it.

    It's also difficult to write code that uses this function correctly, since you might get a number or you might get None — and because you only get None when the input is bogus, and people tend not to think too much about failure cases, chances are you'll end up writing code that assumes a number comes back.

    Returning values of different types can be useful sometimes, but returning a value that can be a valid value or an error indicator is always a bad idea. It's harder to handle correctly, it's less likely that you will handle it correctly, and it's exactly the problem exceptions are meant to solve.

  4. There's no reason for this to be a function in the first place! The error-checking is duplicated effort already provided by float(), and multiplying by ten isn't such a common operation that it needs to be factored out. In fact, this makes the calling code longer.

So I would drop the function and just write this:

result = float('abc') * 10

Bonus: any Python programmer will recognize float, know that it might raise a ValueError, and know to add a try/except if necessary.

I know this was probably an artificial example from a book or homework or something, but this is why considering architecture with trivial examples doesn't really work — if you actually take it seriously, the whole example tends to disappear. :)

like image 30
Eevee Avatar answered Sep 23 '22 15:09

Eevee