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:
-
inspect.getdoc(object): This returns the documentation string for the argument object. The string returned is cleaned up withinspect.cleandoc().>>>acct=test.Account("obi",1000000)>>>importinspect>>>inspect.getdoc(acct)'base class for representing user accounts'>>> -
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.>>>importtest>>>importinspect>>>acct=test.Account("obi",1000000)>>>inspect.getcomments(acct)>>>inspect.getcomments(test)'# python class for intermediate python book\n' -
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 aTypeErrorif the object is a built-in module, class, or function.>>>inspect.getfile(test.Account)'/Users/c4obi/src/test_concat.py'>>> -
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'>>>> -
inspect.getsourcefile(object): This returns the name of the Python source file in which the argument object was defined. This will fail with aTypeErrorif the object is a built-in module, class, or function.>>>inspect.getsourcefile(test_concat.Account)'/Users/c4obi/src/test.py'>>> -
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. AnOSErroris 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) -
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. AnOSErroris raised if the source code cannot be retrieved. Note the difference between this andinspect.getsourcelinesis that this method returns the source code as a single string whileinspect.getsourcelinesreturns a list of source code lines.>>>inspect.getsource(test.Account)'class Account:\n"""base class for representing user accounts"""\nnum_accounts = 0\n\ndef __i\nit__(self, name, balance):\nself.name = name\nself.balance = balance\nAccount.n\um_accounts += 1\n\ndef del_account(self):\nAccount.num_accounts -= 1\n\ndef __getattr__(s\elf, name):\n"""handle attribute reference for non-existent attribute"""\nreturn "Hey I do\nt see any attribute called {}".format(name)\n\ndef deposit(self, amt):\nself.balance = self.b\alance + amt\n\ndef withdraw(self, amt):\nself.balance = self.balance - amt\n\ndef inqui\ry(self):\nreturn "Name={}, balance={}".format(self.name, self.balance)\n'>>> -
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.
-
Signature: This can be used to represent the call signature and return annotation of a function or method. ASignatureobject can be obtained by calling theinspect.signaturemethod with a function or method as argument. Each parameter accepted by the function or method is represented as aParameterobject in the parameter collection of theSignatureobject.Signatureobjects support thebindmethod for mapping from positional and keyword arguments to parameters. Thebind(*args, **kwargs)method will return aBoundsArgumentsobject if*argsand**kwargsmatch thesignatureelse it raises aTypeError. TheSignatureclass also has thebind_partial(*args, **kwargs)method that works in the same way asSignature.bindbut allows the omission of some arguments.>>>deftest(a,b:int)->int:...returna^2+b...>>>inspect.signature(test)<inspect.Signatureobjectat0x101b3c518>>>>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.parametersmappingproxy(OrderedDict([('a',<Parameterat0x101cbf708'a'>),('b',<Parameterat0x101cbf828'b'>)]))>>>str(sig)'(a, b:int) -> int' -
Parameter:Parameterobjects represent function or method arguments within aSignature. Using the previous example for illustration, the parameters of a signature can be accessed as shown in the following snippet.>>>sig.parameters['a']<Parameterat0x101cbf708'a'>>>>sig.parameters['b']<Parameterat0x101cbf828'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
Parameterobject are thenameandkindattributes. The kind attribute is a string that could either bePOSITIONAL_ONLY,POSITIONAL_OR_KEYWORD,VAR_POSITIONAL,KEYWORD_ONLYorVAR_KEYWORD. -
BoundArguments: This is the return value of aSignature.bindorSignature.partial_bindmethod call.>>>sig<inspect.Signatureobjectat0x101b3c5c0>>>>sig.bind(1,2)<inspect.BoundArgumentsobjectat0x1019e6048>A
BoundArgumentsobjects 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.argumentsOrderedDict([('a',1),('b',2)])A
BoundArgumentsobject has the following attributes.-
args: this is a tuple of postional parameter argument values. -
arguments: this is an ordered mapping of parameter argument names to parameter argument values. -
kwargs: this is adictof keyword argument values. -
signature: this is a reference to the parentSignatureobject.
-
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:
-
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'>,)\
)]]]
>>>
-
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).-
argsis a list of the argument names. -
varargsandvarkware the names of the*and**arguments orNone. -
defaultsis 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.
-
annotationsis a dictionary mapping argument names to annotations.
>>>deftest(a:int,b:str='the')->str:...pass...>>>importinspect>>>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'>}) -
-
inspect.getargvalues(frame): This returns information about function arguments that have been passed into a particular frame. The return value is a named tupleArgInfo(args, varargs, keywords, locals).-
argsis a list of the argument names. -
varargsandkeywordsare the names of the*and**arguments orNone. -
localsis the locals dictionary of the given frame.
-
-
inspect.getcallargs(func, *args, **kwds): This binds theargsandkwdsto 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 namedselfto 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. Wheneverfunc(*args, **kwds)would raise an exception because of incompatible signature, an exception of the same type and the same or similar message is raised. -
inspect.getclosurevars(func): This returns the mapping of external name references in function or method,func, to their current values. A named tupleClosureVars(nonlocals, globals, builtins, unbound)is returned.
-
nonlocalsmaps referenced names to lexical closure variables. -
globalsmaps referenced names to the function’s module globals and -
builtinsmaps referenced names to the builtins visible from the function body. -
unboundis 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.
-
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 returnsNone. -
inspect.getframeinfo(frame, context=1): This returns information about the given argument frame or traceback object. A named tupleTraceback(filename, lineno, function, code_context, index)is returned. -
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. -
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. -
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. -
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.