11. Inspecting Objects

The Python inspect module provides powerful methods for interacting with live Python objects. The methods provided by this module help with type checking, sourcecode retrieval, class and function inspection and Interpreter stack inspection. The documentation is the golden source of informtion for this module but a few of the classes and methods in this module are discussed to show the power of this module.

11.1 Handling source code

The inspect module provides functions for accessing the source code of functions, classes and modules. All examples are carried out using our Account class as defined in the following snippet.

    # python class for intermediate python book

    class Account:
    """base class for representing user accounts"""
    num_accounts = 0

    def __init__(self, name, balance):
        self.name = name 
        self.balance = balance 
        Account.num_accounts += 1

    def del_account(self):
        Account.num_accounts -= 1

    def __getattr__(self, name):
        """handle attribute reference for non-existent attribute"""
        return "Hey I dont see any attribute called {}".format(name)

    def deposit(self, amt):
        self.balance = self.balance + amt 

    def withdraw(self, amt):
        self.balance = self.balance - amt 

    def inquiry(self):
        return "Name={}, balance={}".format(self.name, self.balance) =

Some of the methods from the inspect module for handling source code include:

  1. inspect.getdoc(object): This returns the documentation string for the argument object. The string returned is cleaned up with inspect.cleandoc().
     >>> acct = test.Account("obi", 1000000)
     >>> import inspect
     >>> inspect.getdoc(acct)
     'base class for representing user accounts'
     >>> 
    
  2. inspect.getcomments(object): This returns a single string containing any lines of comments. For a class, function or method these are comments immediately preceding the argument object’s source code while for a module, it is the comments at the top of the Python source file.
     >>> import test
     >>> import inspect
     >>> acct = test.Account("obi", 1000000)
     >>> inspect.getcomments(acct)
     >>> inspect.getcomments(test)
     '# python class for intermediate python book\n'
    
  3. inspect.getfile(object): Return the name of the file in which an object was defined. The argument should be a module, class, method, function, traceback, frame or code object. This will fail with a TypeError if the object is a built-in module, class, or function.
     >>> inspect.getfile(test.Account)
     '/Users/c4obi/src/test_concat.py'
     >>> 
    
  4. inspect.getmodule(object): This function attempts to guess the module that the argument object was defined in.
     >>> inspect.getmodule(acct)
     <module 'test' from '/Users/c4obi/src/test.py'>
     >>> 
    
  5. inspect.getsourcefile(object): This returns the name of the Python source file in which the argument object was defined. This will fail with a TypeError if the object is a built-in module, class, or function.
     >>> inspect.getsourcefile(test_concat.Account)
     '/Users/c4obi/src/test.py'
     >>> 
    
  6. inspect.getsourcelines(object): This returns a tuple of the list of source code lines and the line number on which the source code for the argument object begins. The argument may be a module, class, method, function, traceback, frame, or code object. An OSError is raised if the source code cannot be retrieved.
     >>> inspect.getsourcelines(test_concat.Account)
     (['class Account:\n', '    """base class for representing user accounts"""\n', '    num_accounts = 0\n', \
    '\n', '    def __init__(self, name, balance):\n', '        self.name = name \n', '        self.balance = b\
    alance \n', '        Account.num_accounts += 1\n', '\n', '    def del_account(self):\n', '        Account.\
    num_accounts -= 1\n', '\n', '    def __getattr__(self, name):\n', '        """handle attribute reference f\
    or non-existent attribute"""\n', '        return "Hey I dont see any attribute called {}".format(name)\n',\
     '\n', '    def deposit(self, amt):\n', '        self.balance = self.balance + amt \n', '\n', '    def wit\
    hdraw(self, amt):\n', '        self.balance = self.balance - amt \n', '\n', '    def inquiry(self):\n', ' \
           return "Name={}, balance={}".format(self.name, self.balance) \n'], 52)
    
  7. inspect.getsource(object): Return the human readable text of the source code for the argument object. The argument may be a module, class, method, function, traceback, frame, or code object. The source code is returned as a single string. An OSError is raised if the source code cannot be retrieved. Note the difference between this and inspect.getsourcelines is that this method returns the source code as a single string while inspect.getsourcelines returns a list of source code lines.
     >>> inspect.getsource(test.Account)
     'class Account:\n    """base class for representing user accounts"""\n    num_accounts = 0\n\n    def __i\
    nit__(self, name, balance):\n        self.name = name \n        self.balance = balance \n        Account.n\
    um_accounts += 1\n\n    def del_account(self):\n        Account.num_accounts -= 1\n\n    def __getattr__(s\
    elf, name):\n        """handle attribute reference for non-existent attribute"""\n        return "Hey I do\
    nt see any attribute called {}".format(name)\n\n    def deposit(self, amt):\n        self.balance = self.b\
    alance + amt \n\n    def withdraw(self, amt):\n        self.balance = self.balance - amt \n\n    def inqui\
    ry(self):\n        return "Name={}, balance={}".format(self.name, self.balance) \n'
     >>> 
    
  8. inspect.cleandoc(doc): This cleans up indentation from documentation strings that have been indented to line up with blocks of code. Any white-space that can be uniformly removed from the second line onwards is removed and all tabs are expanded to spaces.

11.2 Inspecting Classes and Functions

The inspect module provides some classes and functions for interacting with classes and functions. Signature, Parameter and BoundArguments are important classes in the the inspect module.

  1. Signature: This can be used to represent the call signature and return annotation of a function or method. A Signature object can be obtained by calling the inspect.signature method with a function or method as argument. Each parameter accepted by the function or method is represented as a Parameter object in the parameter collection of the Signature object. Signature objects support the bind method for mapping from positional and keyword arguments to parameters. The bind(*args, **kwargs) method will return a BoundsArguments object if *args and **kwargs match the signature else it raises a TypeError. The Signature class also has the bind_partial(*args, **kwargs) method that works in the same way as Signature.bind but allows the omission of some arguments.
     >>> def test(a, b:int) -> int:
     ...     return a^2+b
     ... 
     >>> inspect.signature(test)
     <inspect.Signature object at 0x101b3c518>
     >>> sig = inspect.signature(test)
     >>> dir(sig)
     ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', \
    '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__\
    reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', '_bind'\
    , '_bound_arguments_cls', '_parameter_cls', '_parameters', '_return_annotation', 'bind', 'bind_partial', '\
    empty', 'from_builtin', 'from_function', 'parameters', 'replace', 'return_annotation']
     >>> sig.parameters
     mappingproxy(OrderedDict([('a', <Parameter at 0x101cbf708 'a'>), ('b', <Parameter at 0x101cbf828 'b'>)]))
     >>> str(sig)
     '(a, b:int) -> int'
    
  2. Parameter: Parameter objects represent function or method arguments within a Signature. Using the previous example for illustration, the parameters of a signature can be accessed as shown in the following snippet.
     >>> sig.parameters['a']
     <Parameter at 0x101cbf708 'a'>
     >>> sig.parameters['b']
     <Parameter at 0x101cbf828 'b'>
     >>> type(sig.parameters['a']
     ... )
     <class 'inspect.Parameter'>
     >>> dir(sig.parameters['a'])
     ['KEYWORD_ONLY', 'POSITIONAL_ONLY', 'POSITIONAL_OR_KEYWORD', 'VAR_KEYWORD', 'VAR_POSITIONAL', '__class__'\
    , '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__\
    hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__',\
     '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', '_annotation', '_def\
    ault', '_kind', '_name', 'annotation', 'default', 'empty', 'kind', 'name', 'replace']
    

    Important attributes of a Parameter object are the name and kind attributes. The kind attribute is a string that could either be POSITIONAL_ONLY, POSITIONAL_OR_KEYWORD, VAR_POSITIONAL, KEYWORD_ONLY or VAR_KEYWORD.

  3. BoundArguments: This is the return value of a Signature.bind or Signature.partial_bind method call.
         >>> sig
         <inspect.Signature object at 0x101b3c5c0>
         >>> sig.bind(1, 2)
         <inspect.BoundArguments object at 0x1019e6048>
    

    A BoundArguments objects contains the mapping of arguments to function or method parameters.

     >>> arg = sig.bind(1, 2)
     >>> dir(arg)
     ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getat\
    tribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__re\
    duce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakr\
    ef__', '_signature', 'args', 'arguments', 'kwargs', 'signature']
     >>> arg.args
     (1, 2)
     >>> arg.arguments
     OrderedDict([('a', 1), ('b', 2)])
    

    A BoundArguments object has the following attributes.

    1. args: this is a tuple of postional parameter argument values.
    2. arguments: this is an ordered mapping of parameter argument names to parameter argument values.
    3. kwargs: this is a dict of keyword argument values.
    4. signature: this is a reference to the parent Signature object.

Interesting functionality can be implemented by making use of these classes mentioned above. For example, we can implement a rudimentary type checker decorator for a function by making use of these classes as shown in the following snippet (thanks to the python cookbook for this).

    from inspect import signature
    from functools import wraps

    def typeassert(*ty_args, **ty_kwargs): 
        def decorate(func):
            # If in optimized mode, disable type checking
            if not __debug__: 
                return func
                # Map function argument names to supplied types
                sig = signature(func)
                bound_types = sig.bind_partial(*ty_args, **ty_kwargs).arguments
            
            @wraps(func)
            def wrapper(*args, **kwargs):
                bound_values = sig.bind(*args, **kwargs)
                # Enforce type assertions across supplied arguments for name, value in bound_values.argume\
nts.items():
                if name in bound_types:
                    if not isinstance(value, bound_types[name]):
                        raise TypeError('Argument {} must be {}'.format(name,bound_types[name]) )
                return func(*args, **kwargs) 
            return wrapper
        return decorate

The defined decorator, type_assert, can then be used to enforce type assertion as shown in the following example.

    >>> @typeassert(str)
    ... def print_name(name):
    ...     print("My name is {}".format(name))
    ... 
    >>> print_name(10)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "/Users/c4obi/src/test_concat.py", line 43, in wrapper
        raise TypeError('Argument {} must be {}'.format(name,bound_types[name]) )
    TypeError: Argument name must be <class 'str'>
    >>> 

The bind_partial is used rather than bind so that we do not have to specify the type for all arguments; the idea behind the partial function from the functools module is also behind this.

The inspect module further defines some functions for interacting with classes and functions. A cross-section of these functions include:

  1. inspect.getclasstree(classes, unique=False): This arranges the list of classes into a hierarchy of nested lists. If the returned list contains a nested list, the nested list contains classes derived from the class whose entry immediately precedes the list. Each entry is a tuple containing a class and a tuple of its base classes.
>>> class Account:
...     pass
... 
>>> class CheckingAccount(Account):
...     pass
... 
>>> class SavingsAccount(Account):
...     pass
... 
>>> import inspect
>>> inspect.getclasstree([Account, CheckingAccount, SavingsAccount])
[(<class 'object'>, ()), [(<class '__main__.Account'>, (<class 'object'>,)), [(<class '__main__.CheckingAc\
count'>, (<class '__main__.Account'>,)), (<class '__main__.SavingsAccount'>, (<class '__main__.Account'>,)\
)]]]
>>> 
  1. inspect.getfullargspec(func): This returns the names and default values of a function’s arguments; the return value is a named tuple of the form: FullArgSpec(args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, annotations).
    • args is a list of the argument names.
    • varargs and varkw are the names of the * and ** arguments or None.
    • defaults is an n-tuple of the default values of the last n arguments, or None if there are no default arguments.
    • kwonlyargs is a list of keyword-only argument names.
    • kwonlydefaults is a dictionary mapping names from kwonlyargs to defaults.
    • annotations is a dictionary mapping argument names to annotations.
    >>> def test(a:int, b:str='the') -> str:
    ...     pass
    ... 
    >>> import inspect
    >>> inspect.getfullargspec(test)
    FullArgSpec(args=['a', 'b'], varargs=None, varkw=None, defaults=('the',), kwonlyargs=[], kwonlydefaults=No\
    ne, annotations={'a': <class 'int'>, 'b': <class 'str'>, 'return': <class 'str'>})
    
  2. inspect.getargvalues(frame): This returns information about function arguments that have been passed into a particular frame. The return value is a named tuple ArgInfo(args, varargs, keywords, locals).
    • args is a list of the argument names.
    • varargs and keywords are the names of the * and ** arguments or None.
    • locals is the locals dictionary of the given frame.
  3. inspect.getcallargs(func, *args, **kwds): This binds the args and kwds to the argument names of the function or method, func, as if it was called with them. For bound methods, bind also the first argument typically named self to the associated instance. A dict is returned, mapping the argument names including the names of the * and ** arguments, if any to their values from args and kwds. Whenever func(*args, **kwds) would raise an exception because of incompatible signature, an exception of the same type and the same or similar message is raised.
  4. inspect.getclosurevars(func): This returns the mapping of external name references in function or method, func, to their current values. A named tuple ClosureVars(nonlocals, globals, builtins, unbound) is returned.
    • nonlocals maps referenced names to lexical closure variables.
    • globals maps referenced names to the function’s module globals and
    • builtins maps referenced names to the builtins visible from the function body.
    • unbound is the set of names referenced in the function that could not be resolved at all given the current module globals and builtins.

The inspect module also supplies functions for accessing members of objects. An example of this is the inspect.getmembers(object[, predicate]) that returns all attribute members of the object arguments; the predicate is an optional value that serves as a filter on the values returned. For example for a given class instance, i, we can get a list of attribute members of i that are methods by making the call inspect.getmembers(i, inspect.ismethod); this returns a list of tuples of the attribute name and attribute object. The following example illustrates this.

    >>> acct = test_concat.Account("obi", 1000000000)
    >>> import inspect
    >>> inspect.getmembers(acct, inspect.ismethod)
    [('__getattr__', <bound method Account.__getattr__ of <test_concat.Account object at 0x101b3c470>>), (\
'__init__', <bound method Account.__init__ of <test_concat.Account object at 0x101b3c470>>), ('del_account\
', <bound method Account.del_account of <test_concat.Account object at 0x101b3c470>>), ('deposit', <bound \
method Account.deposit of <test_concat.Account object at 0x101b3c470>>), ('inquiry', <bound method Account\
.inquiry of <test_concat.Account object at 0x101b3c470>>), ('withdraw', <bound method Account.withdraw of \
<test_concat.Account object at 0x101b3c470>>)]

The inspect module has predicates for this method that include isclass, ismethod, isfunction, isgeneratorfunction, isgenerator, istraceback, isframe, iscode, isbuiltin, isroutine, isabstract, ismethoddescriptor.

11.3 Interacting with Interpreter Stacks

The inspect module also provides functions for dealing with interpeter stacks. The interpeter stack is composed of frames. All the functions below return a tuple of the frame object, the filename, the line number of the current line, the function name, a list of lines of context from the source code, and the index of the current line within that list. The provided functions enable user to inspect and manipulate the frame records.

  1. inspect.currentframe(): This returns the frame object for the caller’s stack frame. This function relies on stack frame support in the interpreter and this is not guaranteed to exist in all implementations of Python for example stackless python. If running in an implementation without Python stack frame support this function returns None.
  2. inspect.getframeinfo(frame, context=1): This returns information about the given argument frame or traceback object. A named tuple Traceback(filename, lineno, function, code_context, index) is returned.
  3. inspect.getouterframes(frame, context=1): This returns a list of frame records for a given frame argument and all outer frames. These frames represent the calls that led to the creation of the argument frame. The first entry in the returned list represents the argument frame; the last entry represents the outermost call on the arugment frame’s stack.
  4. inspect.getinnerframes(traceback, context=1): This returns a list of frame records for a traceback’s frame and all inner frames. These frames represent calls made as a consequence of frame. The first entry in the list represents traceback; the last entry represents where the exception was raised.
  5. inspect.stack(context=1): This returns a list of frame records for the caller’s stack. The first entry in the returned list represents the caller; the last entry represents the outermost call on the stack.
  6. inspect.trace(context=1): This returns a list of frame records for the stack between the current frame and the frame in which an exception currently being handled was raised in. The first entry in the list represents the caller; the last entry represents where the exception was raised.