16

I have a return statement inside a try clause:

def f():
    try:
        return whatever()
    finally:
        pass # How do I get what `whatever()` returned in here?

Is it possible to get the return value inside the finally clause?

This is more of a theoretical question, so I'm not looking for a workaround like saving it to a variable.

Ram Rachum
  • 84,019
  • 84
  • 236
  • 374

5 Answers5

16

No, it isn't - the finally clause's content is independent from return mechanics; if you do want to see what value is returned you'd have to do as you mentioned and explicitly save it somewhere that's in-scope.

Amber
  • 507,862
  • 82
  • 626
  • 550
  • How certain are you of this? Are you familiar with the internal mechanics of how Python handles this? – Ram Rachum Dec 25 '12 at 21:39
  • 1
    Pretty certain - there's no way to get at it from Python, at least. You might be able to hack around with stuff via C, but the closest you get to this info in Python is [`inspect`](http://docs.python.org/2/library/inspect.html) and you'll notice there's no listing of return value information there. – Amber Dec 25 '12 at 21:42
  • @RamRachum: this also holds true for Java and is a classical mechanism of `try//finally`. To give a concrete example, a common idiom is for a method to throw an exception because it could not read the content of a stream, all the while closing the stream in the `finally` block; only if the `try` block completes successfully is a result returned, otherwise an exception is thrown. – fge Dec 25 '12 at 21:43
7

Do you absolutely have to "get in" after the return statement?

If changes allowed before the return statement, sys.settrace() is all you need.

Getting the value after return:

I think, in stackless Python, you should be able to do that. "threads" can be pickled in stackless, and about-to-be-returned value, aka top of value stack, ought to be there.

In CPython, I couldn't find a way peek at top of value stack yet.

  • dynamically changing frame.f_lasti is not allowed
  • dynamically changing frame.f_code is not allowed
  • dynamically changing frame.f_trace is allowed but doesn't seem to help
  • set tracing function from within finally block doesn't catch actual return event after return statement was "already executed"
  • with statment doesn't catch return value
  • I assume caller ignores f's return value, thus introspection or tracing the caller doesn't help
  • I assume whatever() has side effects and cannot be called again
  • debuggers, at least those that I tried, don't get return value (?); debuggers written in Python use sys.settrace and/or last exception, neither of these contains return value on stack.

Of course, everything is possible with ctypes or C-level extension, here's a quick demo:

"""
valuestack.py: Demo reading values from Python value stack
Offsets are derived for CPython-2.7.2, 64-bit, production build
"""
import ctypes, inspect, random

def id2obj(i):
    """convert CPython `id` back to object ref, by temporary pointer swap"""
    tmp = None,
    try:
        ctypes.cast(id(tmp), ctypes.POINTER(ctypes.c_ulong))[3] = i
        return tmp[0]
    finally:
        ctypes.cast(id(tmp), ctypes.POINTER(ctypes.c_ulong))[3] = id(None)

def introspect():
    """pointer on top of value stack is id of the object about to be returned
    FIXME adjust for sum(vars, locals) in introspected function
    """
    fr = inspect.stack()[1][0]
    print "caught", id2obj(ctypes.cast(id(fr), ctypes.POINTER(ctypes.c_ulong))[47])

def value():
    tmp = random.random()
    print "return", tmp
    return tmp

def foo():
    try:
        return value()
    finally:
        introspect()

if __name__ == "__main__":
    foo()

Works with Python-2.7.2 in 64-bit mode as shipped with osx:

air:~ dima$ python valuestack.py 
return 0.959725159294
caught 0.959725159294
Dima Tisnek
  • 11,241
  • 4
  • 68
  • 120
1

It's not possible. It may be if you consider giant freaking hacks, like inspecting the bytecode and fiddling with the frame object. I'm not sure this one would even make sense, as the return value isn't in a local and I don't think you can access the stack of intermediate values (which is where the return value is saved) via frame objects.

Perhaps you can use ctypes plus the C API, but that would probably be very shaky as well, and would require being very familiar with the implementation of finally. I don't know remotely enough about it to judge how feasible this is, but needless to say, this would fall squarely outside of what Python code can do.

And then there's the added problem that there might not be a return value (if whatever() throws an exception)! I guess you could easily detect that condition though, using sys.exc_info().

1
def f():
    InvalidFoo = object()
    foo = InvalidFoo
    try:
        foo = whatever()
        return foo
    finally:
        if foo is not InvalidFoo:
            # look at foo
Michael Merickel
  • 23,153
  • 3
  • 54
  • 70
0

You can view the data but not recommended to change the values

import sys
try:
    pass
expect:
    pass
finally:
    local_vars = sys._getframe(0).f_locals
    for var in local_vars:
        logger.debug(f"{var}: {local_vars[var]}")
vivek87799
  • 11
  • 1