Assuming we have a function that updates a bunch of internal values within a class like so:
class MyClass:
def __init__():
self.counter = 0
self.condition_1 = True
self.condition_2 = False
def update():
if self.condition_1:
if self.condition_2:
self.counter += 2
# return or not?
else:
self.counter += 1
# return or not?
else:
self.counter -= 1
# return or not?
Would the update function be executed faster with or without a return statement within it (after updating variables)? Or would it be 100% the same? (unlikely for me)
I know this sounds like a trivial/dumb question to ask without context, but consider that this function is being called repeatedly thousands of times so slight increase in performance within the function can have a large impact on how fast the whole program takes to execute.
In my real program, the conditions within the update function are very complex and more nested; the program processes a lot of data as well.
You can look at the produced bytecode to answer this:
With explicit return
s:
4 0 LOAD_CONST 1 (0)
2 STORE_FAST 0 (counter)
5 4 LOAD_FAST 0 (counter)
6 LOAD_CONST 1 (0)
8 COMPARE_OP 2 (==)
10 POP_JUMP_IF_FALSE 22 (to 44)
6 12 LOAD_FAST 0 (counter)
14 LOAD_CONST 2 (1)
16 COMPARE_OP 2 (==)
18 POP_JUMP_IF_FALSE 16 (to 32)
7 20 LOAD_FAST 0 (counter)
22 LOAD_CONST 3 (2)
24 INPLACE_ADD
26 STORE_FAST 0 (counter)
8 28 LOAD_CONST 0 (None)
30 RETURN_VALUE
10 >> 32 LOAD_FAST 0 (counter)
34 LOAD_CONST 2 (1)
36 INPLACE_ADD
38 STORE_FAST 0 (counter)
11 40 LOAD_CONST 0 (None)
42 RETURN_VALUE
13 >> 44 LOAD_FAST 0 (counter)
46 LOAD_CONST 2 (1)
48 INPLACE_SUBTRACT
50 STORE_FAST 0 (counter)
14 52 LOAD_CONST 0 (None)
54 RETURN_VALUE
Without explicit return
s:
4 0 LOAD_CONST 1 (0)
2 STORE_FAST 0 (counter)
5 4 LOAD_FAST 0 (counter)
6 LOAD_CONST 1 (0)
8 COMPARE_OP 2 (==)
10 POP_JUMP_IF_FALSE 22 (to 44)
6 12 LOAD_FAST 0 (counter)
14 LOAD_CONST 2 (1)
16 COMPARE_OP 2 (==)
18 POP_JUMP_IF_FALSE 16 (to 32)
7 20 LOAD_FAST 0 (counter)
22 LOAD_CONST 3 (2)
24 INPLACE_ADD
26 STORE_FAST 0 (counter)
28 LOAD_CONST 0 (None)
30 RETURN_VALUE
10 >> 32 LOAD_FAST 0 (counter)
34 LOAD_CONST 2 (1)
36 INPLACE_ADD
38 STORE_FAST 0 (counter)
40 LOAD_CONST 0 (None)
42 RETURN_VALUE
13 >> 44 LOAD_FAST 0 (counter)
46 LOAD_CONST 2 (1)
48 INPLACE_SUBTRACT
50 STORE_FAST 0 (counter)
52 LOAD_CONST 0 (None)
54 RETURN_VALUE
Although it's hard to see manually, they result in identical bytecode in this particular case, with Python 3.10 (verified with diffchecker; although any text comparison tool would work). The only difference is the loading of None
is associated with a line with the explicit return
version.
Both result in the same compiled bytes:
>>> dis.Bytecode(update).codeobj.co_code
b'd\x01}\x00|\x00d\x01k\x02r\x16|\x00d\x02k\x02r\x10|\x00d\x027\x00}\x00d\x00S\x00|\x00d\x037\x00}\x00d\x00S\x00|\x00d\x038\x00}\x00d\x00S\x00'
Test code:
import dis
def update():
counter = 0
if counter == 0:
if counter == 1:
counter += 2
return
else:
counter += 1
return
else:
counter -= 1
return
dis.dis(update)