Armin Ronacher

return_value_used for Python

written by Armin Ronacher, on Saturday, May 30, 2009 12:35.

Georg found a way today to get the names for a variable in Python. Motivated by this incredible hack I gave porting runkit_return_value_used from PHP to Python a try. What this function does in PHP is finding out if the return value of a function is used or not.

So if you're evil you could write a function like this in PHP:

<?php

function the_today() {
  $today = date("l jS \\of F Y h:i:s A");
  if (runkit_return_value_used())
    return $today;
  echo $today
}

?>
<p>Today is: <?php the_today(); ?></p>

This would print the current date if the function is invoked without assigning the return value to something but will return the current date as string otherwise. I have no idea how this works in PHP but you can have that functionality in Python as well.

import sys, dis

def rvused():
    frame = sys._getframe(2)
    remaining = frame.f_code.co_code[frame.f_lasti:]
    try:
        next_code = remaining[ord(remaining[0]) >= dis.HAVE_ARGUMENT
                              and 3 or 1]
    except IndexError:
        return True
    return ord(next_code) != dis.opmap['POP_TOP']

You can then use it like this:

>>> def foo():
...  if rvused():
...   print "my return value is used"
...   return 42
...  print "my return value is not used"
... 
>>> def test():
...  print foo()
...  foo()
... 
>>> test()
my return value is used
42
my return value is not used

The implementation of the function is pretty simple actually. The rvused function goes two stack frames back, which is the stackframe of the function calling rvused. Then we look at the next bytecode after the last executed instruction (f_lasti) which should be the one that handles the return value of our function. If the bytecode is POP_TOP it means we have an unused value on the stack the virtual machine has to throw away. In that case we let the function return False to signal an unused return value.

How this works becomes obvious if we look at the bytecode for test:

>>> dis.dis(test)
  2           0 LOAD_GLOBAL              0 (foo)
              3 CALL_FUNCTION            0
              6 PRINT_ITEM          
              7 PRINT_NEWLINE       

  3           8 LOAD_GLOBAL              0 (foo)
             11 CALL_FUNCTION            0
             14 POP_TOP             
             15 LOAD_CONST               0 (None)
             18 RETURN_VALUE        

The last instruction in both cases is CALL_FUNCTION. The next one is PRINT_ITEM in the first case which means that python will print the last value on the stack, which is the return value of the function. In the second case the opcode after the function call is POP_TOP which tells the VM to throw away the result.

Oh. And please don't use that in your code. It was just a stupid experiment to find out if it was possible to port that abomination of a function from PHP to Python. Maybe it helps for debugging though.

Comments

  1. This is thoroughly evil and you should be ashamed of yourself! ;-)

    Clever though. :-D

    —  Michael Foord on Saturday, May 30, 2009 20:26 #

  2. I feel dirty just for having read that. :-)

    —  James Eagan on Tuesday, June 2, 2009 16:56 #

  3. If this were Perl, that'd be at the end of every subroutine (see wantarray). +1 for Python. =P

    —  anon on Tuesday, July 7, 2009 20:07 #

Leave a Reply