问题
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.
回答1:
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.
回答2:
Do you absolutely have to "get in" after the return statement?
Changes allowed before the statement, sys.settrace() is all you need.
Only 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
回答3:
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()
.
回答4:
def f():
InvalidFoo = object()
foo = InvalidFoo
try:
foo = whatever()
return foo
finally:
if foo is not InvalidFoo:
# look at foo
来源:https://stackoverflow.com/questions/14034156/python-is-it-possible-to-access-the-return-value-inside-the-finally-clause