ALL Python functions return something and this article explains how and why.
print
return something?Does the built-in print
return something when you call it?
It doesn't!..
Or does it?
If you open a Python REPL and call print
, you get the value you printed and nothing more:
>>> print("Hello, world!")
Hello, world!
However, when you call a function that returns something, the value that is returned is shown in the output:
>>> def return_5():
... print("Hello, world!")
... return 5
...
>>> return_5()
Hello, world!
5
So, it looks like return_5
returns 5
and that print
returns nothing...
But that's not quite true. And the reason why it looks true is because of the way the REPL handles a certain value...
None
in the REPLOpen the REPL, type None
, and press Enter.
What do you expect to see?
What if you assign None
to a variable and then type the name of the variable into the REPL?
What if you create a function that returns None
and then call it?
In all of the scenarios above, the REPL shows nothing:
>>> None
>>> my_value = None
>>> my_value
>>> def return_none():
... return None
...
>>> return_none()
Notice that the REPL never shows any output.
That's because the REPL treats the value None
in a special way and omits it from outputs!
If you want to see these values, you can print them, for example:
>>> print(None)
None
>>> print(my_value)
None
>>> print(return_none())
None
So, we are now aware that the value None
is handled differently inside the REPL.
Now, this is going to be a very important piece of information for what comes next.
The truth of the matter is that ALL functions return something.
And the ones that look like they don't?
That's because they return None
.
For example, here I'm assigning the return value of calling print
to a variable and then printing the value of that variable:
>>> print_return = print("Hello, world!")
Hello, world!
>>> print(print_return)
None
As another example, the method .append
of Python lists also looks like it doesn't return anything.
Wrong!
It returns None
:
>>> my_list = [73, 42]
>>> append_return = my_list.append(0)
>>> print(append_return)
None
If print
or append
didn't return a thing, we wouldn't be able to assign the results of calling them to variables.
But we can.
And we can print those values.
So, we know that those functions always return something.
But now, things get even more interesting.
See, maybe print
and append
end with return None
, right?
So, what if you write a function that doesn't have return None
at the end?
None
Let me define an empty function:
def empty():
pass
If you use the module dis
to dissect that function, you will see the instructions that Python runs under the hood when you call the function.
For such a simple function, the result of dissecting it tells a lot about how Python works:
>>> import dis
>>> def empty():
... pass
...
>>> dis.dis(empty)
1 0 RESUME 0
2 2 LOAD_CONST 0 (None)
4 RETURN_VALUE
Notice the three words RESUME
, LOAD_CONST
, and RETURN_VALUE
?
Those are the three instructions that Python runs when going over the function empty
.
The RESUME
does some set-up for when you enter the function.
But the LOAD_CONST
and RETURN_VALUE
came out of nowhere!
See the (None)
in front of LOAD_CONST
?
What that tells us is that Python will return the value None
from the function.
Perhaps this is easier to interpret if we compare it to a function that just returns a simple string:
def hello_world():
return "Hello, world!"
If you dissect this function, here is what you get:
>>> def hello_world():
... return "Hello, world!"
...
>>> dis.dis(hello_world)
1 0 RESUME 0
2 2 LOAD_CONST 1 ('Hello, world!')
4 RETURN_VALUE
See how remarkably similar the two dissect outputs are?
The only difference is the value that is shown in front of LOAD_CONST
, which is None
for the function empty
and 'Hello, world!'
for hello_world
.
So, the instructions for empty
resemble the instructions of a function whose only job is to return a specific value.
To really drive the point home, let us compare the dissect of empty
with the result of dissecting a function that only contains the statement return None
:
>>> def return_none(): | >>> def empty():
... return None | ... pass
... | ...
>>> dis.dis(return_none) | >>> dis.dis(empty)
1 0 RESUME 0 | 1 0 RESUME 0
|
2 2 LOAD_CONST 0 (None) | 2 2 LOAD_CONST 0 (None)
4 RETURN_VALUE | 4 RETURN_VALUE
This shows that Python essentially injected the return None
in your empty function, but Python will do it in other cases too.
Whenever Python detects that your function doesn't return anything explicitly, Python will add a statement return None
implicitly.
As a challenge, write a function that does something but without returning a value.
Use the module dis
to dissect it and find the return None
that Python inserted in the bytecode.
Below is an example of such a function.
Take a look at the very end of the list of instructions to find the return None
!
>>> def greet(name):
... print(f"Hello, {name}!")
...
>>> dis.dis(greet)
1 0 RESUME 0
2 2 LOAD_GLOBAL 1 (NULL + print)
14 LOAD_CONST 1 ('Hello, ')
16 LOAD_FAST 0 (name)
18 FORMAT_VALUE 0
20 LOAD_CONST 2 ('!')
22 BUILD_STRING 3
24 PRECALL 1
28 CALL 1
38 POP_TOP
40 LOAD_CONST 0 (None)
42 RETURN_VALUE
The explanation that follows is an educated guess based on my experience.
We've seen that Python does some funky things with return None
in some functions.
But why?
The reason Python will add return None
in certain cases is because Python wants to ensure that calling a function in Python is always an expression.
In programming, loosely speaking, an expression is a piece of code that produces a value that can be used for other things.
For example, a mathematical calculation like 3 + x
is an expression because there is a result associated with the 3 + x
and you can use that result for other things:
Next to expressions, we have statements.
Again, loosely speaking, statements are pieces of code that do things with the language.
For example, an if
is a statement that determines whether or not a piece of code should run.
The if
itself doesn't produce a value, and that is why it is not an expression.
In Python, many function calls are expressions because they produce values.
For example, calling max(3, x)
will produce a value, so max(3, x)
is an expression.
So, for the sake of consistency, Python will make sure that all function calls are always expressions.
This simplifies the language a bit, as it would be fairly awkward to have some functions be expressions and some others be statements.
Or even worse, functions that could be both!
For example, if Python didn't add a return None
, the function below would be an expression some times and a statement some other times:
def sometimes_expression(num):
if num > 0:
return 1
print("No dice.")
So, Python just went with the homogenous solution. All function calls are expressions. And that's it.
After all, the Zen of Python says “Special cases aren't special enough to break the rules.”.
Now you know that all Python functions return something:
print
or append
); andreturn
statements.This knowledge should give you a clearer understanding of how Python functions work. It should also give you a deeper appreciation for the work that Python does to be a homogenous language with as few special cases as possible. Having few(er) special cases means the language is easier to learn and, ultimately, easier to master.
+35 chapters. +400 pages. Hundreds of examples. Over 30,000 readers!
My book “Pydon'ts” teaches you how to write elegant, expressive, and Pythonic code, to help you become a better developer. >>> Download it here 🐍🚀.