Capturing invoking method information in Python

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:

  1. the instance object of the invoker (the subject),
  2. the name of the method (the name), and
  3. 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.

 1 import inspect
 2 from functools import partial, wraps
 3 
 4 def capture_invoking_method_info(
 5     name = '_invoker_info'):
 6     """
 7     Decorator which captures invoking method
 8     information and places it in the keyword
 9     arguments of the decorated method, by
10     default as the keyword argument '_invoker_info'
11     which can be set via the 'name' argument. The
12     information captured consists of the subject
13     (who contains the method which is invoking),
14     the invoking method name, and the stack frame
15     of the invocation.
16     """
17     def _capture_controller(wrapped_method):
18         def _capture_controlled(*m_args,
19             **m_kwargs):
20             frame = inspect.currentframe().f_back
21             # If the invocation is from the shell, or
22             # the invoker frame's locals are missing,
23             # the subject becomes None.
24             if len(frame.f_code.co_varnames) > 0:
25                 subject = frame.f_locals[
26                     frame.f_code.co_varnames[0]]
27             else:
28                 subject = None
29             m_kwargs[name] = (subject,
30                 frame.f_code.co_name, frame)
31             return wrapped_method(*m_args,
32                 **m_kwargs)
33         return wraps(wrapped_method)(
34             _capture_controlled)
35     return partial(_capture_controller)

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 class A(object):
 2 
 3   def x(self):
 4     return self.y()
 5 
 6   @capture_invoking_method_info()
 7   def y(self, _invoker_info):
 8     return _invoker_info
 9 
10 class B(A):
11   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.

Comments

comments powered by Disqus