Behavior of python method in absence of return statement
Question:
I have a question related to change in program behavior
the absence of return statement leads to in a python method.
The count method below prints number of digits in a given integer.
With the below chunk of code I get the result as 4, which is
the expected result.
def count(x,acc=0):
if x==0:
return acc
return count(x/10,acc+1)
print "Count is %s" %(count(1234))
Result: Count is 4
If I modify the above method so that the last statement does not contain
the ‘return’ statement the result I get is ‘None’.
def count(x,acc=0):
if x==0:
return acc
count(x/10,acc+1)
print "Count is %s" %(count(1234))
Result: Count is None
(The version of Python I used is: 2.7.3)
Does the above behavior results due to the fact that Python doesn’t do tail call optimization or is there any other reasoning involved ?
A similar chunk of code in perl (which AFAIK doesn’t do tail call optimization) provides
the expected result without ‘return’ being part of the last statement.
sub counter {
my ($n,$acc) = @_;
return $acc if ($n==0);
counter(int($n/10), $acc+1);
}
print "Count is:" . counter(1234,0) ."n"
Result: Count is:4
(The versions of Perl I ran above code chunk are : 5.14.4 and 5.8.5).
My questions are:
- Is Tail Call optimization the reason for the behavior shown in above chunk of Python code.
- If that were the case then why behavior of perl code differs, which doesn’t do TCO either.
Answers:
Doesn’t have to do with the missing of tail optimization at all. Functions in Python need the keyword return
explicitly, otherwise is assumed their return None
.
I know Ruby doesn’t behave that way, it returns the value of the last executed expression. With Perl it must be the same.
It is nothing that clever, just the fact that Python programs behave that way 🙂
See the disassembly for both Python functions. You may see how the one having the return
value actually calls the function and return the value on top of the stack. The one that doesn’t have it, see that have two instructions after the funciont call, which load constant None
and return it.
def count(x,acc=0):
if x==0:
return acc
return count(x/10,acc+1)
def count2(x,acc=0):
if x==0:
return acc
count(x/10,acc+1)
In [7]: import dis
In [8]: dis.dis(count)
2 0 LOAD_FAST 0 (x)
3 LOAD_CONST 1 (0)
6 COMPARE_OP 2 (==)
9 POP_JUMP_IF_FALSE 16
3 12 LOAD_FAST 1 (acc)
15 RETURN_VALUE
4 >> 16 LOAD_GLOBAL 0 (count)
19 LOAD_FAST 0 (x)
22 LOAD_CONST 2 (10)
25 BINARY_DIVIDE
26 LOAD_FAST 1 (acc)
29 LOAD_CONST 3 (1)
32 BINARY_ADD
33 CALL_FUNCTION 2
36 RETURN_VALUE
In [9]: dis.dis(count2)
2 0 LOAD_FAST 0 (x)
3 LOAD_CONST 1 (0)
6 COMPARE_OP 2 (==)
9 POP_JUMP_IF_FALSE 16
3 12 LOAD_FAST 1 (acc)
15 RETURN_VALUE
4 >> 16 LOAD_GLOBAL 0 (count)
19 LOAD_FAST 0 (x)
22 LOAD_CONST 2 (10)
25 BINARY_DIVIDE
26 LOAD_FAST 1 (acc)
29 LOAD_CONST 3 (1)
32 BINARY_ADD
33 CALL_FUNCTION 2
36 POP_TOP
37 LOAD_CONST 0 (None)
40 RETURN_VALUE
Without the return
, the only case where your return something if when acc == 0
; for any other call to your count
recursive method, you don’t return anything, hence the None
received.
Unlike other languages (scala, apparently perl as well, probably others), python does not return the last statement by default, you have to call return
explicitely.
Tail call elimination does not prevent Perl’s semantics (though without Perl semantics, you’d have far fewer tail calls since every function would implicitly end with return;
). It’s purely a language design choice. The reason Perl and Python differ is that they weren’t designed by the same people.
The syntax for returning a value varies by language simply due to choice. The designers of Python opted to require an explicit return
statement. In QBasic, you had to assign the value to return to a variable that shared the same name as the function.
Functions in Python without a return
statement return the value None
.
In this case, you are only returning a value when x == 0
, therefore the None
that you get. Check the Python tutorial for more info: http://docs.python.org/2/tutorial/controlflow.html#defining-functions
I have a question related to change in program behavior
the absence of return statement leads to in a python method.
The count method below prints number of digits in a given integer.
With the below chunk of code I get the result as 4, which is
the expected result.
def count(x,acc=0):
if x==0:
return acc
return count(x/10,acc+1)
print "Count is %s" %(count(1234))
Result: Count is 4
If I modify the above method so that the last statement does not contain
the ‘return’ statement the result I get is ‘None’.
def count(x,acc=0):
if x==0:
return acc
count(x/10,acc+1)
print "Count is %s" %(count(1234))
Result: Count is None
(The version of Python I used is: 2.7.3)
Does the above behavior results due to the fact that Python doesn’t do tail call optimization or is there any other reasoning involved ?
A similar chunk of code in perl (which AFAIK doesn’t do tail call optimization) provides
the expected result without ‘return’ being part of the last statement.
sub counter {
my ($n,$acc) = @_;
return $acc if ($n==0);
counter(int($n/10), $acc+1);
}
print "Count is:" . counter(1234,0) ."n"
Result: Count is:4
(The versions of Perl I ran above code chunk are : 5.14.4 and 5.8.5).
My questions are:
- Is Tail Call optimization the reason for the behavior shown in above chunk of Python code.
- If that were the case then why behavior of perl code differs, which doesn’t do TCO either.
Doesn’t have to do with the missing of tail optimization at all. Functions in Python need the keyword return
explicitly, otherwise is assumed their return None
.
I know Ruby doesn’t behave that way, it returns the value of the last executed expression. With Perl it must be the same.
It is nothing that clever, just the fact that Python programs behave that way 🙂
See the disassembly for both Python functions. You may see how the one having the return
value actually calls the function and return the value on top of the stack. The one that doesn’t have it, see that have two instructions after the funciont call, which load constant None
and return it.
def count(x,acc=0):
if x==0:
return acc
return count(x/10,acc+1)
def count2(x,acc=0):
if x==0:
return acc
count(x/10,acc+1)
In [7]: import dis
In [8]: dis.dis(count)
2 0 LOAD_FAST 0 (x)
3 LOAD_CONST 1 (0)
6 COMPARE_OP 2 (==)
9 POP_JUMP_IF_FALSE 16
3 12 LOAD_FAST 1 (acc)
15 RETURN_VALUE
4 >> 16 LOAD_GLOBAL 0 (count)
19 LOAD_FAST 0 (x)
22 LOAD_CONST 2 (10)
25 BINARY_DIVIDE
26 LOAD_FAST 1 (acc)
29 LOAD_CONST 3 (1)
32 BINARY_ADD
33 CALL_FUNCTION 2
36 RETURN_VALUE
In [9]: dis.dis(count2)
2 0 LOAD_FAST 0 (x)
3 LOAD_CONST 1 (0)
6 COMPARE_OP 2 (==)
9 POP_JUMP_IF_FALSE 16
3 12 LOAD_FAST 1 (acc)
15 RETURN_VALUE
4 >> 16 LOAD_GLOBAL 0 (count)
19 LOAD_FAST 0 (x)
22 LOAD_CONST 2 (10)
25 BINARY_DIVIDE
26 LOAD_FAST 1 (acc)
29 LOAD_CONST 3 (1)
32 BINARY_ADD
33 CALL_FUNCTION 2
36 POP_TOP
37 LOAD_CONST 0 (None)
40 RETURN_VALUE
Without the return
, the only case where your return something if when acc == 0
; for any other call to your count
recursive method, you don’t return anything, hence the None
received.
Unlike other languages (scala, apparently perl as well, probably others), python does not return the last statement by default, you have to call return
explicitely.
Tail call elimination does not prevent Perl’s semantics (though without Perl semantics, you’d have far fewer tail calls since every function would implicitly end with return;
). It’s purely a language design choice. The reason Perl and Python differ is that they weren’t designed by the same people.
The syntax for returning a value varies by language simply due to choice. The designers of Python opted to require an explicit return
statement. In QBasic, you had to assign the value to return to a variable that shared the same name as the function.
Functions in Python without a return
statement return the value None
.
In this case, you are only returning a value when x == 0
, therefore the None
that you get. Check the Python tutorial for more info: http://docs.python.org/2/tutorial/controlflow.html#defining-functions