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.
Asked By: sateesh

||

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
Answered By: Paulo Bu

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.

Answered By: Gorkk

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.

Answered By: ikegami

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

Answered By: Alvaro
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.