You can use ast or compiler module to dig through the compiled code and find out places where functions are explicitly called.
You can also just compile code with compile() with ast flag and get it parsed as abstract syntax tree. Then you go see what is called where within it.
But you can track down all what is happening during the codes execution using some tricks from sys, inspect and traceback modules.
For example, you can set your trace function that will snatch each interpreter frame before letting it be executed:
import dis
import sys
def tracefunc (frame, evt, arg):
print frame.f_code.co_filename, frame.f_lineno, evt
print frame.f_code.co_name, frame.f_code.co_firstlineno
#print dis.dis(f.f_code)
sys.settrace(tracefunc)
After this code, every step done will be printed with file that contains the code, line of the step, where the code object begins and it will disassemble it so you can see all that is being done or will be done in background too (if you uncomment it).
If you want to match executed bytecode with Python code, you can use tokenize module.
You make a cache of tokenized files as they appear in trace and snatch the Python code out of the corresponding lines whenever needed.
Using all mentioned stuff you can do wanders including writing byte code decompiler, jumping all over your code like with goto in C,
forcefully interrupting threads (not recommended if you don't exactly know what you are upto), track which function called your function (nice for streaming servers to recognize clients catching up their parts of stream),
and all sorts of crazy stuff.
Advanced crazy stuff I have to say.
DON'T GO MESSING code flow in SUCH A MANNER UNLESS IT IS ABSOLUTELY NECESSARY and you don't know EXACTLY what you are doing.
I'll get down voted just because I mentioned such things are even possible.
Example for dynamically detecting which instance of client() tries to get the content:
from thread import get_ident
import sys
class Distributer:
def read (self):
# Who called me:
cf = sys._current_frames()
tid = get_ident() # Make it thread safe
frame = cf[tid]
# Now, I was called in one frame back so
# go back and find the 'self' variable of a method that called me
# and self, of course, contains the instance from which I was called
client = frame.f_back.f_locals["self"]
print "I was called by", client
class Client:
def __init__ (self, name):
self.name = name
def snatch (self):
# Now client gets his content:
content.read()
def __str__ (self):
return self.name
content = Distributer()
clients = [Client("First"), Client("Second"), Client("Third"), Client("Fourth"), Client("Etc...")]
for client in clients:
client.snatch()
Now, you write this within the tracing function instead of fixed method, but cleverly, not relying on variable names but on addresses and stuff and you can track what happens when and where. Big job, but possible.