Login | Register For Free | Help
Search for: (Advanced)

Mailing List Archive: Python: Python

finding out the call (and not only the caller)

 

 

Python python RSS feed   Index | Next | Previous | View Threaded


f.guerrieri at gmail

Oct 7, 2007, 2:47 PM

Post #1 of 4 (120 views)
Permalink
finding out the call (and not only the caller)

Hi,

Today I've been thinking a bit about the "python internals". Inspired
by this recipe:
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66062
I found out a little problem which haven't been able to solve.
In short, is there a way to find out how a given name lookup was started?
It is not enough to know the name of the caller as given by the recipe.

a little example:
import inspect

class test(object):
def __init__(self, a_list):
self.my_list = a_list
def info(self):
for counter, item in
enumerate(inspect.getouterframes(inspect.currentframe())):
print counter, item
return self.my_list
data = property(info)

if __name__ == '__main__':
a = test([1111,2222])
def g(a_seq):
for item in a_seq:
print item, '\n'
g(a.data)

This prints
0 (<frame object at 0x00B58B08>, 'myfile.py', 10, 'info', ['
for counter, item in
enumerate(inspect.getouterframes(inspect.currentframe())):\n'], 0)
1 (<frame object at 0x00A5B000>, 'myfile.py', 38, '<module>', ['
g(a.data)\n'], 0)
1111
2222

What I would like is a reference to g itself, and not only to
'<module>' which is the caller according to f_code.co_name.
I thought of manually parsing the string ' g(a.data)\n' to extract
the name but I'm sure that it would be a rather fragile approach, and
so I decided that it was time to ask for help :-)
To 'get a feeling', I tried to disassemble the code:

code = compile('g(a.data)', 'test', 'single')
dis.dis(code)

and this is the result

0 LOAD_NAME 0 (g)
3 LOAD_NAME 1 (a)
6 LOAD_ATTR 2 (data)
9 CALL_FUNCTION 1
12 PRINT_EXPR
13 LOAD_CONST 0 (None)
16 RETURN_VALUE

So ... I'm looking for the first name loaded. Is there a reference to
it, somewhere?

Francesco
--
http://mail.python.org/mailman/listinfo/python-list


nnorwitz at gmail

Oct 8, 2007, 11:38 PM

Post #2 of 4 (107 views)
Permalink
Re: finding out the call (and not only the caller) [In reply to]

On Oct 7, 2:47 pm, "Francesco Guerrieri" <f.guerri...@gmail.com>
wrote:
> Hi,
>
> Today I've been thinking a bit about the "python internals". Inspired
> by this recipe:http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66062
> I found out a little problem which haven't been able to solve.
> In short, is there a way to find out how a given name lookup was started?

If you constrain the problem, yes, it's possible. However, the
solution below is limited to CPython and has other limits.

> It is not enough to know the name of the caller as given by the recipe.
>
> a little example:
> import inspect
>
> class test(object):
> def __init__(self, a_list):
> self.my_list = a_list
> def info(self):
> for counter, item in
> enumerate(inspect.getouterframes(inspect.currentframe())):
> print counter, item
> return self.my_list
> data = property(info)
>
> if __name__ == '__main__':
> a = test([1111,2222])
> def g(a_seq):
> for item in a_seq:
> print item, '\n'
> g(a.data)
>
> This prints
> 0 (<frame object at 0x00B58B08>, 'myfile.py', 10, 'info', ['
> for counter, item in
> enumerate(inspect.getouterframes(inspect.currentframe())):\n'], 0)
> 1 (<frame object at 0x00A5B000>, 'myfile.py', 38, '<module>', ['
> g(a.data)\n'], 0)
> 1111
> 2222
>
> What I would like is a reference to g itself, and not only to
> '<module>' which is the caller according to f_code.co_name.
> I thought of manually parsing the string ' g(a.data)\n' to extract
> the name but I'm sure that it would be a rather fragile approach, and
> so I decided that it was time to ask for help :-)
> To 'get a feeling', I tried to disassemble the code:
>
> code = compile('g(a.data)', 'test', 'single')
> dis.dis(code)
>
> and this is the result
>
> 0 LOAD_NAME 0 (g)
> 3 LOAD_NAME 1 (a)
> 6 LOAD_ATTR 2 (data)
> 9 CALL_FUNCTION 1
> 12 PRINT_EXPR
> 13 LOAD_CONST 0 (None)
> 16 RETURN_VALUE
>
> So ... I'm looking for the first name loaded. Is there a reference to
> it, somewhere?

#!/usr/bin/python

import sys, opcode


def WhoCalledMe(a, *args, **kwargs):
# Get the calling frame and the bytecode index of the last
instruction.
f = sys._getframe(1)
lasti = f.f_lasti
code = f.f_code.co_code

# TODO: Needs to be extended for the other 3 variants of
CALL_FUNCTION.
assert ord(code[lasti]) == opcode.opmap['CALL_FUNCTION']
# Get the number of arguments the function was called with.
num_args = ord(code[lasti+1])
num_kwargs = ord(code[lasti+2])

# Get the instruction that loaded the object being called.
# Work backwards from the function call. 3 is the size of each
LOAD_* op
# (assuming EXTENDED_ARG is not used). 1 for the previous opcode,
# number of positional arguments, and 2 for kwargs (1 for the name,
# 1 for the value).
i = lasti - 3 * (1 + num_args + 2 * num_kwargs)
# Again, this assumes EXTENDED_ARG is not used.
load_index = ord(code[i+1]) + (ord(code[i+2]) << 8)

# Another assumption that isn't always true, but convenient for our
case.
opname = opcode.opname[ord(code[i])]
assert opname.startswith('LOAD_')

# Get the name of the variable and its value.
name = value = None
if opname in ('LOAD_FAST', 'LOAD_NAME'):
try:
name = f.f_code.co_varnames[load_index]
value = f.f_locals[name]
except LookupError:
assert opname == 'LOAD_NAME'
# opname must be LOAD_NAME and the variable is a global, ignore
it.

if opname in ('LOAD_GLOBAL', 'LOAD_NAME') and not name:
try:
name = f.f_code.co_names[load_index]
value = f.f_globals[name]
except LookupError:
print 'Uh-oh. Should have found the name in globals.'
# TODO: handle LOAD_CONST, LOAD_DEREF and LOAD_CLOSURE.

print 'Called by', name, 'value is', value


def main():
WhoCalledMe(0)

MeDammit = WhoCalledMe
MeDammit(1, k1=1, k2=2)


if __name__ == '__main__':
main()

WhoCalledMe(0)

MeDammit = WhoCalledMe
MeDammit(1, k1=1, k2=2)

# end of script

If you run the scrit above, it should print something like:

Called by WhoCalledMe value is <function WhoCalledMe at
0x2ac718dff6e0>
Called by MeDammit value is <function WhoCalledMe at 0x2ac718dff6e0>
Called by WhoCalledMe value is <function WhoCalledMe at
0x2ac718dff6e0>
Called by MeDammit value is <function WhoCalledMe at 0x2ac718dff6e0>

The code doesn't handle all the cases (e.g., nested functions), but
gives you an idea of what can be done and where to look for the info
(ie, in the stack frame and how to parse the byte codes to some
extent). For nested functions, look in co_consts.

Bytecodes are specific to CPython. This code won't work on
IronPython, Jython, or PyPy. This code really shouldn't be used for
production. However, it demonstrates some of the info available.

n

--
http://mail.python.org/mailman/listinfo/python-list


cfbolz at gmx

Oct 28, 2007, 1:42 PM

Post #3 of 4 (80 views)
Permalink
Re: finding out the call (and not only the caller) [In reply to]

Hi Neal,

nnorwitz [at] gmail wrote:
> The code doesn't handle all the cases (e.g., nested functions), but
> gives you an idea of what can be done and where to look for the info
> (ie, in the stack frame and how to parse the byte codes to some
> extent). For nested functions, look in co_consts.
>
> Bytecodes are specific to CPython. This code won't work on
> IronPython, Jython, or PyPy. This code really shouldn't be used for
> production. However, it demonstrates some of the info available.

Actually that's not true for PyPy. PyPy uses CPython's bytecode, so the
example works just fine.

Cheers,

Carl Friedrich Bolz
--
http://mail.python.org/mailman/listinfo/python-list


cfbolz at gmx

Oct 28, 2007, 1:42 PM

Post #4 of 4 (83 views)
Permalink
Re: finding out the call (and not only the caller) [In reply to]

Hi Neal,

nnorwitz [at] gmail wrote:
> The code doesn't handle all the cases (e.g., nested functions), but
> gives you an idea of what can be done and where to look for the info
> (ie, in the stack frame and how to parse the byte codes to some
> extent). For nested functions, look in co_consts.
>
> Bytecodes are specific to CPython. This code won't work on
> IronPython, Jython, or PyPy. This code really shouldn't be used for
> production. However, it demonstrates some of the info available.

Actually that's not true for PyPy. PyPy uses CPython's bytecode, so the
example works just fine.

Cheers,

Carl Friedrich Bolz

--
http://mail.python.org/mailman/listinfo/python-list

Python python RSS feed   Index | Next | Previous | View Threaded
 
 


Interested in having your list archived? Contact Gossamer Threads
 
  Web Applications & Managed Hosting Powered by Gossamer Threads Inc.