Exploring Python objects through introspection

When I started coding in Python I learned mostly by copying code snippets from books, tutorials, and blogs. Something that I don’t remember seeing very much was the topic of introspection, which is basically the inspection of live objects. Python is great at introspection, and offers built-in functions to help pull out the useful information.

Lets walk through some examples inspecting the objects of the platform module. Feel free to follow along in a python interpreter:


jake@debian-jake:~$ python3
Python 3.6.7 (default, Oct 21 2018, 08:08:16) 
[GCC 8.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import platform
>>>

The simplest way to see inside an object is using the dir() function, which will return a list of objects contained within another object:

This is certainly a lot to look at, and we don’t know what type of objects they are. We see some internal methods (starts with single underscore), some magic/dunder methods (starts and ends with double underscore), and plenty of other objects that could be classes, methods, or properties. A basic way to explore these objects is to use the type() function to learn more about each object:


>>> type(platform.python_version)
<class 'function'>
>>>

You could go through and do this with each object that looks interesting to you, but it would probably be smarter to script it. Even smarter than that would be to have a built-in library that does the heavy lifting, which is exactly what the inspect module does. Here are some ways to use inspect:

To do the equivalent of dir() on the object, and then a type() on everything in the returned list, you can use inspect.getmembers(). Getmembers() returns a list of tuples, where each tuple contains the name of the object and the type of the object. If the object is a property, the second item in the tuple will have the value of the property:

 

This returns a bunch of information, but it’s easier to sift through if we don’t look at the internal and dunder methods.


>>> for object, value in inspect.getmembers(platform):
...     if not object.startswith('_'): # Exclude internal or dunder methods
...             print(object, value)
... 
DEV_NULL /dev/null
architecture 
collections <module 'collections' from '/usr/lib/python3.6/collections/__init__.py'>
dist 
java_ver 
libc_ver 
linux_distribution 
<snip>
uname 
uname_result <class 'platform.uname_result'>
version 
warnings <module 'warnings' from '/usr/lib/python3.6/warnings.py'>
win32_ver 
>>>

If we want to see the value of the functions, or understand which ones require parameters, we can call getmembers with the isfunction() predicate:


>>> for object, value in inspect.getmembers(platform, inspect.isfunction):
...     if not object.startswith('_'): # Exclude internal or dunder methods
...             try:                   # Prints functions that don’t require params
...                     print(object, platform.__getattribute__(object)())
...             except Exception as e: # Prints functions that require params 
...                     print(object, e)
... 
architecture ('64bit', 'ELF')
dist ('debian', '9.6', '')
java_ver ('', '', ('', '', ''), ('', '', ''))
libc_ver ('glibc', '2.25')
linux_distribution ('debian', '9.6', '')
mac_ver ('', ('', '', ''), '')
machine x86_64
node debian-jake
platform Linux-4.9.0-8-amd64-x86_64-with-debian-9.6
popen popen() missing 1 required positional argument: 'cmd'
processor 
python_branch 
python_build ('default', 'Oct 21 2018 08:08:16')
python_compiler GCC 8.2.0
python_implementation CPython
python_revision 
python_version 3.6.7
python_version_tuple ('3', '6', '7')
release 4.9.0-8-amd64
system Linux
system_alias system_alias() missing 3 required positional arguments: 'system', 'release', and 'version'
uname uname_result(system='Linux', node='debian-jake', release='4.9.0-8-amd64', version='#1 SMP Debian 4.9.130-2 (2018-10-27)', machine='x86_64', processor='')
version #1 SMP Debian 4.9.130-2 (2018-10-27)
win32_ver ('', '', '', '')
>>>

The inspect module has some pretty cool features, including the ability to read the source code of various files using the getsource() function:


>>> print(''.join(inspect.getsourcelines(platform.uname)[0]))
def uname():

    """ Fairly portable uname interface. Returns a tuple
        of strings (system, node, release, version, machine, processor)
        identifying the underlying platform.

        Note that unlike the os.uname function this also returns
        possible processor information as an additional tuple entry.

        Entries which cannot be determined are set to ''.

    """
    global _uname_cache
    no_os_uname = 0

    if _uname_cache is not None:
        return _uname_cache

    processor = ''

    # Get some infos from the builtin os.uname API...
    try:
<snip>
>>>

While reading the source is a pretty cool feature, it’s probably more practical to read the docstrings so that you can have a description of that particular object. I wrote a script that does this, get_module_docstring.py. Here is what the code looks like:

And here is the output:

As you can probably guess, the inspect module has many more functions. You can read about them in the docs, or you can just use introspection and explore them yourself. The above code is available on my GitHub, and I’ll probably add more introspection scripts from time to time.

One comment

Leave a Reply

Your email address will not be published. Required fields are marked *