Dr Alexis Petrounias

Information Systems Engineer

Europe

Capturing invoking method information in Python

written on in categories Python Programming

The following decorator will capture all available information regarding the method which invokes the decorated method (the invoked method).

The available information consists of the following:

  • a) the instance object of the invoker (the subject),
  • b) the name of the method (the name), and
  • c) the invocation frame in the current stack (the frame).

This information is passed to the invoked method through a keyword argument, by default _invoker_info, which can be overridden through the name argument to the decorator.

Decorator

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import inspect
from functools import partial, wraps

def capture_invoking_method_info(
    name = '_invoker_info'):
    """
    Decorator which captures invoking method
    information and places it in the keyword
    arguments of the decorated method, by
    default as the keyword argument '_invoker_info'
    which can be set via the 'name' argument. The
    information captured consists of the subject
    (who contains the method which is invoking),
    the invoking method name, and the stack frame
    of the invocation.
    """
    def _capture_controller(wrapped_method):
        def _capture_controlled(*m_args,
            **m_kwargs):
            frame = inspect.currentframe().f_back
            # If the invocation is from the shell, or
            # the invoker frame's locals are missing,
            # the subject becomes None.
            if len(frame.f_code.co_varnames) > 0:
                subject = frame.f_locals[
                    frame.f_code.co_varnames[0]]
            else:
                subject = None
            m_kwargs[name] = (subject,
                frame.f_code.co_name, frame)
            return wrapped_method(*m_args,
                **m_kwargs)
        return wraps(wrapped_method)(
            _capture_controlled)
    return partial(_capture_controller)

Demonstration

The following example demonstrates its use. Assume a class A which defines two methods, x and y, where x invokes y, and y is decorated with the above decorator:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class A(object):

  def x(self):
    return self.y()

  @capture_invoking_method_info()
  def y(self, _invoker_info):
    return _invoker_info

class B(A):
  pass

An instance object b of class B will return the following captured information tuple when its x method is invoked:

>>> b.x()
(<__main__.B object at 0x10162ee90>, 'x', <frame object at 0x101560090>)

And, since we are in the interactive Python shell, the same object b will return the following captured information tuple when its y method is invoked:

>>> b.y()
(None, '<module>', <frame object at 0x101562750>)

Predictably, the subject here is None, as the invocation was performed from the shell and not another instance object.