Wandering Thoughts archives

2008-04-28

Abusing Python frame and code objects

Suppose that you want to find the value of the first argument to the function that called your caller. While the inspect module documents the types involved, it doesn't really explain what the members are; here's what I've worked out.

Start with a frame, for example from calling inspect.currentframe() to get your own frame and then backing up a couple of frames to your caller's caller via f_back. The values of all of the function's local variables are in f_locals, a name to value dictionary, but that doesn't tell you which ones are the function arguments and which are ordinary local variables. For that you need to go to the function's code object via f_code.

The names of all the function's local variables are in the co_varnames tuple, in the order that they occur; this puts the function arguments at the front. How many function arguments there are is co_argcount; this includes keyword arguments but not *args or **kwargs stuff.

(There is a complicated naming scheme to handle nested arguments that I am not going to get into here. If you need to care, you should use the functions in the inspect module.)

So let's put all that together and write a function the returns true if its caller's caller had a given first argument (ignoring nested arguments and so on):

import inspect
def isfirstarg(obj):
    fr = inspect.currentframe()
    try:
        fr = fr.f_back.f_back
        if fr is None:
            return False
        if not fr.f_locals:
            return False
        fl = fr.f_locals
        fc = fr.f_code
        if fc.co_argcount < 1:
            return False
        fa = fl[fc.co_varnames[0]]
        return bool(fa is obj)
    finally:
        del fr

Can you use this to solve the mixin over-tracing problem? Probably not; there are any number of plausible functions that take an object as their first argument, despite not being methods. If you assume your code follows conventions, you can avoid false positives by making isfirstarg() also check that the name of the first argument is 'self'.

(As before, all of this is probably CPython specific, especially since a bunch of this is intimately tied to the CPython bytecode format.)

python/AbusingFrames written at 23:10:21; Add Comment


Page tools: See As Normal.
Search:
Login: Password:
Atom Syndication: Recent Pages, Recent Comments.

This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.