Question

I can't figure out how to inspect a currently executing function using inspect / inspect_shell

I'm guessing it involves walking the frame hierarchy using getinnerframe and getouterframe, but I'm confused on several issues.

Given this example nine.py:

import inspect_shell
import time

def number_nine():
    x = 9
    while x==9:
        time.sleep(1)

number_nine()
print x

I would like to inspect the value of x or even possibly change it to cause the function to return and print the new value.

First I launch nine.py, then in a separate command window, using inspect_shell, I see that getinnerframes does not work on the current frame (it needs a trace (perhaps?)) and the current frame has no "trace". and getouterframes (in case I'm thinking about this backwards) seems to get only frames that have nothing to do with my function.

>> Inspect Shell v1.0
>> https://github.com/amoffat/Inspect-Shell

localhost:1234> import inspect

localhost:1234> f = inspect.currentframe()

localhost:1234> inspect.getinnerframes(f)
Traceback (most recent call last):
  File "C:\Users\Paul\Desktop\inspect_shell.py", line 143, in run_repl
    try: exec compile(data, "<dummy>", "single") in f_globals, f_globals
  File "<dummy>", line 1, in <module>
  File "C:\Python26\lib\inspect.py", line 942, in getinnerframes
    framelist.append((tb.tb_frame,) + getframeinfo(tb, context))
AttributeError: 'frame' object has no attribute 'tb_frame'


localhost:1234> dir(f)
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__',
'__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
'__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'f_back', 'f_builtins', 
'f_code', 'f_exc_traceback', 'f_exc_type', 'f_exc_value', 'f_glo
bals', 'f_lasti', 'f_lineno', 'f_locals', 'f_restricted', 'f_trace']

localhost:1234> print f.f_trace
None

localhost:1234> inspect.getouterframes(f)
[(<frame object at 0x0237D470>, '<dummy>', 1, '<module>', None, None), 
(<frame object at 0x02375938>, 'C:\\Users\\Paul\\Desktop\\inspect_shell.py', 14
3, 'run_repl', ['                    try: exec compile(data, "<dummy>", "single") in
f_globals, f_globals\n'], 0), (<frame object at 0x023A2C30>, 'C:\
\Python26\\lib\\threading.py', 484, 'run', 
['                self.__target(*self.__args, **self.__kwargs)\n'], 0), 
(<frame object at 0x02397F28>, 'C:\
\Python26\\lib\\threading.py', 532, '__bootstrap_inner', 
['                self.run()\n'], 0), (<frame object at 0x023A9D68>, 
'C:\\Python26\\lib\\thre
ading.py', 504, '__bootstrap', ['            self.__bootstrap_inner()\n'], 0)]
Was it helpful?

Solution

Author of Inspect Shell here :) You're probably going to need to make x accessible from the global namespace. Inspect Shell essentially drops you into the global namespace of the running script, so if some data can't be reached from there, it's going to be really tricky to get to it.

So really the suggestion is, make x global, then you should be able to alter it and your number_nine() function will return.

Hope that helps!

OTHER TIPS

This is kind of tricky, but given the source code (or a good python decompiler), you can do this:

in 'nine.py':

def number_nine():
    x = 9
    if x == 9:
        print 'x is Nine!'
    else:
        print 'x:', x

Your evil code:

from nine import number_nine

We need to use ast, which is Abstract Syntax Trees:

import inspect
import ast

Now we get the source and transform it to an ast:

# Assuming you have the source, we can generate AST from it
nine_src = inspect.getsource(number_nine)
nine_ast = ast.parse(nine_src)

Isolate the specific statement you want to change:

# This is the Assign object, which represents the 'x = 9' line
# Try to run it interactivly and see how it looks...
x_assign = nine_ast.body[0].body[0]

# Prints 'x'
print x_assign.targets[0].id
# Prints 9
print x_assign.value.n

And alter it as you see fit:

# Change the value of x
# Notice, that we change the assignment itself, a.k.a `x = 9` is now `x = "It's a trap!"`
x_assign.value.n = "It's a trap!"

Now all that is left to do is to compile your modified ast object to something more useful:

# Compile the new function
new_nine = compile(nine_ast, 'new_nine', 'exec')

You can use simple exec (which will replace 'number_nine' if it is in globals), or exec in, and place it in a temp module:

# Now we need to execute our litle new_nine (which is a code object)
# This to create the modified version in 'm'
import types
m = types.ModuleType('m', 'The m module')
exec new_nine in m.__dict__
m.number_nine()

# Or this to create it in the global scope
exec new_nine
number_nine()

And bang! it prints x: It's a trap!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top