9.4. Return Values Should Always Have the Same Data Type

Python is a dynamically typed language, which means that Python func- tions and methods are free to return values of any data type. But to make your functions more predictable, you should strive to have them return val- ues of only a single data type.

For example, here’s a function that, depending on a random number, returns either an integer value or a string value:

>>> import random
>>> def returnsTwoTypes():
>>>
>>>     if random.randint(1, 2) == 1:
>>>
>>>         return 42
>>>
>>>     else:
>>>
>>>         return 'forty two'

When you’re writing code that calls this function, it can be easy to forget that you must handle several possible data types. To continue this example, say we call returnsTwoTypes() and want to convert the number that it returns to hexadecimal:

>>> hexNum = hex(returnsTwoTypes())
>>> hexNum
'0x2a'

Python’s built-in hex() function returns a string of a hexadecimal number of the integer value it was passed. This code works fine as long as returnsTwoTypes() returns an integer, giving us the impression that this code is bug free. But when returnsTwoTypes() returns a string, it raises an exception:

>>> hexNum = hex(returnsTwoTypes())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object cannot be interpreted as an integer

Of course, we should always remember to handle every possible data type that the return value could have. But in the real world, it’s easy to for- get this. To prevent these bugs, we should always attempt to make functions return values of a single data type. This isn’t a strict requirement, and some- times there’s no way around having your function return values of different data types. But the closer you get to returning only one type, the simpler and less bug prone your functions will be.

There is one case in particular to be aware of: don’t return None from your function unless your function always returns None . The None value is the only value in the NoneType data type. It’s tempting to have a function return None to signify that an error occurred (I discuss this practice in the next section, “Raising Exceptions vs. Returning Error Codes”), but you should reserve returning None for functions that have no meaningful return value.

The reason is that returning None to indicate an error is a common source of uncaught ‘NoneType’ object has no attribute exceptions:

>>> import random
>>> def sometimesReturnsNone():
>>>
>>>     if random.randint(1, 2) == 1:
>>>
>>>         return 'Hello!'
>>>
>>>     else:
>>>
>>>         return None
>>> returnVal = sometimesReturnsNone()
>>> returnVal.upper()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'upper'

This error message is rather vague, and it could take some effort to trace its cause back to a function that normally returns an expected result but could also return None when an error happens. The problem occurred because sometimesReturnsNone() returned None , which we then assigned to the returnVal variable. But the error message would lead you to think the prob- lem occurred in the call to the upper() method.

In a 2009 conference talk, computer scientist Tony Hoare apologized for inventing the null reference (the general analogous value to Python’s None value) in 1965, saying “I call it my billion dollar mistake. […] I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last 40 years.” You can view his full talk online at https://autbor.com/ billiondollarmistake.