Python: inspect where an exception raise would go
Take t开发者_如何学Chis code:
def A():
try:
B()
except Exception:
pass
def B():
C()
def C():
print exception_handling_pointer()
A()
The function exception_handling_pointer
should return me a pointer to the function where this specific exception would be checked first for being handled. I.e., in this case, I would expect the output to be sth. like:
<function A ...>
How can I implement the function exception_handling_pointer
?
You can't decide where a Exception will get handled without actually raising the Exception. This is easy to see here:
try:
raise input('Raise which?')
except input('Catch which?') as e:
pass`
Any function that does what you want would have to predict user input here. The whole endeavor is futile and Python has no support for it.
Anyways, i hope you ask only out of interest ...
This is a pretty silly thing to do and most people would say that it can't be done (THC4k gives compelling evidence of this for the general cace) but it does sound fun and should be perfectly doable in many real use-cases.
step 1. You need to step back through the frames. Get the first one with sys._getframe
or inspect.currentframe
(don't tell anyone, the second seems aliased to the first). Then you can iterate through them with f.f_back
step 2. Each one will have a f.f_lasti
instruction. This is the last instruction that was executed in the frame. You'll have to save it. Now go backwords through the bytecode - f.f_code.co_code
- and look for a SETUP_EXCEPT
opcode with an argument that jumps to after f.f_lasti`. The jump point is the exception handling.
step 3. This is where it gets fuzzier. The key is that the actual comparison operation will be a COMPARE_OP
with a 10 as its argument. In all cases that I've seen, it's followed by a POP_JUMP_IF_FALSE
. This will jump to the next except
clause or the finally
clause. It will be preceded by the code that loads loads the exceptions onto the stack. If there is only one, then it will be a straight LOAD_GLOBAL
or a LOAD_GLOBAL
or LOAD_FAST
(depending if the module with the exceptions is global or local) followed by a LOAD_ATTR
. If there are multiple exceptions being matched then there will be a sequence of load operations followed by a BUILD_TUPLE
(idiomatic) or BUILD_LIST
(some other weird or non-idiomatic situation).
The point is that you can go through the LOAD_X
instructions and compare the name to the exception that you're matching. Note that you're comparing name only. If they've reassigned the name, you're SOL.
step 4. Let's assume that you found a match. Now you need the function object. The best way that I can think of to do this follows (I reserve the right to update): The f.f_code
will have a co_filename
attribute. You can loop through sys.modules
and each one will have __name__
attribute. You can compare the two keeping in mind that you should use __name__.endswith(co_filename)
. When you get a match, you can loop over the modules functions and compare their f.func_code.co_firstlineno
attribute with the frames f.f_lineno
attribute. When you get a match, you have your function. You should loop over the methods of each class in the module as well. There's the possibility that the handling is occurring in some nested function in which case, I can't currently think of a sensible thing to do. (It would be a whole other bytecode hack and would itself be flakey)
step 5. Profit.
This should give you the general idea of how to go about doing this. There are all sorts of corner cases where you wont be able to do it but in any normal use-case, you should be able to pull it off. If you write code that depends on being able to do it, it will break though. This is sort of "Do it because I can" sort of thing.
精彩评论