Domanda

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.

È stato utile?

Soluzione

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.

Altri suggerimenti

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

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().

def f():
    InvalidFoo = object()
    foo = InvalidFoo
    try:
        foo = whatever()
        return foo
    finally:
        if foo is not InvalidFoo:
            # look at foo
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top