Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Python - inspect - Get full caller name (package.module.function)
# Public Domain, i.e. feel free to copy/paste
# Considered a hack in Python 2
import inspect
def caller_name(skip=2):
"""Get a name of a caller in the format module.class.method
`skip` specifies how many levels of stack to skip while getting caller
name. skip=1 means "who calls me", skip=2 "who calls my caller" etc.
An empty string is returned if skipped levels exceed stack height
"""
stack = inspect.stack()
start = 0 + skip
if len(stack) < start + 1:
return ''
parentframe = stack[start][0]
name = []
module = inspect.getmodule(parentframe)
# `modname` can be None when frame is executed directly in console
# TODO(techtonik): consider using __main__
if module:
name.append(module.__name__)
# detect classname
if 'self' in parentframe.f_locals:
# I don't know any way to detect call from the object method
# XXX: there seems to be no way to detect static method call - it will
# be just a function call
name.append(parentframe.f_locals['self'].__class__.__name__)
codename = parentframe.f_code.co_name
if codename != '<module>': # top level usually
name.append( codename ) # function or a method
del parentframe
return ".".join(name)

mgedmin commented Feb 28, 2013

This is exactly why I was overjoyed when http://www.python.org/dev/peps/pep-3155/ appeared. Python 3.3+ only, though.

Otherwise the only sure way to figure out the class name I can think of would be using parentframe.f_code.co_filename and co_firstlineno, then parsing the source file (if it's available) and seeing what classes where defined in which code lines.

Owner

techtonik commented Feb 28, 2013

__qname__ sounds better to me, but it still it doesn't solve the problem of orientation in the frame completely. For example, what is the convention to identify the top level? I understand that this top level differs depending on how the code is called - usual 'python module.py', console, exec() - what else is possible? What is the difference? Why it should be different? These questions I don't have answers for now.

Very useful, thank you.

Thanks!

👏 thanks so much for this!

Safihre commented Oct 15, 2017

6 years later, but still want to say thanks!

cfobel commented Jan 27, 2018

Thanks very much! This is a very useful function!

I have made one minor change to line 14. I noticed that when I was using this function to set the name of a logger at many points in my program, things seemed to start running pretty slowly. After profiling, it looks like this is due to line 14 (i.e., the call to inspect.stack). Fortunately, a simplified function can be used instead, as shown below:

...
>>> import sys
>>> import inspect
>>>
>>> def stack_(frame):
...     framelist = []
...     while frame:
...         framelist.append(frame)
...         frame = frame.f_back
...     return framelist
...
>>> %timeit stack_(sys._getframe(1))
100000 loops, best of 3: 3.09 µs per loop
>>> %timeit inspect.stack()
100 loops, best of 3: 8.36 ms per loop

While the simple stack_ function is not a drop-in replacement for inspect.stack, it provides all the functionality required for caller_name and is over 2000x faster.

Here is a modified version of the code, including an additional function _L which can be used to return a logger named based on the current method/function context:

# Public Domain, i.e. feel free to copy/paste
# Considered a hack in Python 2
import inspect
import logging
import sys


def _L(skip=0):
    '''Shorthand to get logger for current function frame.'''
    return logging.getLogger(caller_name(skip + 1))


def caller_name(skip=2):
    """Get a name of a caller in the format module.class.method

       `skip` specifies how many levels of stack to skip while getting caller
       name. skip=1 means "who calls me", skip=2 "who calls my caller" etc.

       An empty string is returned if skipped levels exceed stack height
    """
    def stack_(frame):
        framelist = []
        while frame:
            framelist.append(frame)
            frame = frame.f_back
        return framelist

    stack = stack_(sys._getframe(1))
    start = 0 + skip
    if len(stack) < start + 1:
        return ''
    parentframe = stack[start]

    name = []
    module = inspect.getmodule(parentframe)
    # `modname` can be None when frame is executed directly in console
    # TODO(techtonik): consider using __main__
    if module:
        name.append(module.__name__)
    # detect classname
    if 'self' in parentframe.f_locals:
        # I don't know any way to detect call from the object method
        # XXX: there seems to be no way to detect static method call - it will
        #      be just a function call
        name.append(parentframe.f_locals['self'].__class__.__name__)
    codename = parentframe.f_code.co_name
    if codename != '<module>':  # top level usually
        name.append(codename)  # function or a method
    del parentframe
    return ".".join(name)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment