Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I get the name of the class of a bound method from the interpreter stack?

I have an awesome little function that looks like this:

def verbose_print(message, *args, **kwargs):
    """Prints `message` with a helpful prefix when in verbose mode

    Args:
        message (str): The message to print. Can be a format string, e.g.
            `A %s with some %s in it`
        *args: Variables for the message format
        **kwargs: Keyword variables for the message format
    """

    # Only print when in verbose mode
    if not config.verbose:
        return

    # Ready a prefix for the message
    try:
        s = inspect.stack()
        module_name = inspect.getmodule(s[1][0]).__name__
        func_name = s[1][3]
        prefix = '### %s->%s' % (module_name, func_name)
    except Exception as e:
        prefix = '### [stack unavailable]'

    if args:
        message = message % args
    elif kwargs:
        message = message % kwargs

    print '%s: %s' % (prefix, message)

The point of the function is that I can call it from anywhere with a message, and if my project config file is set to verbose mode, all the messages will be printed with a helpful prefix to show where it was called. Here's an example of some output:

### avesta.webserver->check_login: Checking login for client at 127.0.0.1
### avesta.webserver->check_login: Found credentials cookie with username: tomas, token: blablabla
### avesta.webserver->check_login: Login valid, refreshing session
### avesta.webserver->get_flash_memory: Fetched flash data: None
### avesta.webserver->get: Fetched data from empty path ('previous_values', 'name'), returning ''
### avesta.webserver->get: Fetched data from empty path ('previous_values', 'description'), returning ''
### avesta.webserver->get: Fetched data from empty path ('validation_errors', 'name'), returning ''

The format is "### module->function: message".

Now most of the time this is really helpful, but it's not perfect. In the example above, the "get" function is actually a bound method of a class, but that's not visible. What I'm trying to accomplish is that when a function is a bound method, I print with this format instead:

"### module->ClassName.function"

But the problem is:

  1. I only get the function name from the stack, so I can't really check if it's a bound method
  2. Even if I had the function reference, how would I extrapolate the class name it's bound to?

Thanks for any answers that can help me figure this out.

like image 292
Hubro Avatar asked Jul 28 '12 23:07

Hubro


1 Answers

I thought this was going to be easy, but it turned out to be a bit complicated. If you have a reference to the bound method, you can get its class name via boundMethod.im_class.__name__. However, when you're grabbing the stack, you can't easily get a reference to the bound method, just to stack frames.

However all is not lost! The inspect module can get you function arguments from a stack frame using the getargvalues function. You do have to cheat a little, by relying on the convention that methods always have their first argument named "self". You can check for that, then grab the "self" value from the function's locals dict, and from there it's easy to get the class name. Try replacing your current try block with his code:

s = inspect.stack()
module_name = inspect.getmodule(s[1][0]).__name__
func_name = s[1][3]
arginfo = inspect.getargvalues(s[1][0])
if len(arginfo.args) > 0 and arginfo.args[0] == "self":
    func_name = "%s.%s" (arginfo.locals["self"].__class__.__name__, func_name)
prefix = '### %s->%s' % (module_name, func_name)
like image 171
Blckknght Avatar answered Sep 28 '22 05:09

Blckknght