IHE Python course, 2017

Introspection

T.N.Olsthoorn, Feb 27, 2017

Getting the signature of a function or method, getting the methods associated with a class and the attributes of a module is importand and wanted all the time. Searchting the internet and its forums for manuals, documents, tutorials and answers to specific question is often also required, and a requesting the answer for your specific question is often faster done and answer obtained on a forum than looked up in book or a pdf.file, it still comes handy to have tools and techiques readily available for introspection.

A dir(obj) works, but seems a mess at first sight. This is however only the case until you see how to deal with it. A simple list comprehension can filter out exactly what you need into a nice list.

Autocompletion when pressingn in the editor

In Ipython (and, therefore also in this notebook), the first way of introspection is tab completion. Start typing the name of the object and press tap. You can do that at any position of the screen you're busy typing. Python will show a list of available options.

If you press enter do that at the dot like str.<tab> you get a list of all the attributes of the objects. There are many and you can scroll through them. Try it


In [69]:
#str.<tab>  # remove the `#` and the text <tab> and press <tab>

Once the sought one found, press <tab> again to see the attributes associated with the particular choice.


In [70]:
from scipy import special

# try in steps
#scipy.special.airy.identity.conjugate

Getting help on the fly, and callable's signature

To get help on any function in put a question mark immediately behind it and press <shift><enter> to execute the cell


In [71]:
#str.islower?<shift><enter>

The help appears in a separate window at the bottom of the screen. Press the small at the corner of that window x to remove it again if it takes up too much space at your screen.

A question mark in the front alsow works.

Two question marks yields the underlying python code if it is available.

Alternatively use help(obj)


In [75]:
help(str.lower)


Help on method_descriptor:

lower(...)
    S.lower() -> str
    
    Return a copy of the string S converted to lowercase.

Inspection of objects with dir().

It's often desired to have an overview of all the attributes of an object, without the "private" ones, i.e. those starting with on or two underscores. One way to do this is with the function dir(). However, dir() tends to spawn an overwhelming mess, at least at first sight. This flood can be handled and ordered, however. An easy way is using a simple list comprehension to filter out all objects that start with the underscore.


In [41]:
# This already works for any object (string, list, function, module etc).

anObj = str

[f for f in dir(anObj) if not f.startswith('_')]  # is a useful compact overview of public attributes.


Out[41]:
['capitalize',
 'casefold',
 'center',
 'count',
 'encode',
 'endswith',
 'expandtabs',
 'find',
 'format',
 'format_map',
 'index',
 'isalnum',
 'isalpha',
 'isdecimal',
 'isdigit',
 'isidentifier',
 'islower',
 'isnumeric',
 'isprintable',
 'isspace',
 'istitle',
 'isupper',
 'join',
 'ljust',
 'lower',
 'lstrip',
 'maketrans',
 'partition',
 'replace',
 'rfind',
 'rindex',
 'rjust',
 'rpartition',
 'rsplit',
 'rstrip',
 'split',
 'splitlines',
 'startswith',
 'strip',
 'swapcase',
 'title',
 'translate',
 'upper',
 'zfill']

Using inspect.getmembers to inspect which attributes are callable or not

Sometimes you need to know which attributes of an object are callable and which are not.

Lets take the os.path module as an example, which as both callable and non callable attributes.

One way to do is similar to the above, by using the function getmembers() from the module inspect. This function yields a list of tuples where each tuple contains two items: the name of the attribute and the attribute itself:


In [45]:
from inspect import getmembers

In [48]:
anObj = os.path

[f for f in getmembers(anObj) if not f[0].startswith('_')]  # is a useful compact overview of public attributes.


Out[48]:
[('abspath', <function posixpath.abspath>),
 ('altsep', None),
 ('basename', <function posixpath.basename>),
 ('commonpath', <function posixpath.commonpath>),
 ('commonprefix', <function genericpath.commonprefix>),
 ('curdir', '.'),
 ('defpath', ':/bin:/usr/bin'),
 ('devnull', '/dev/null'),
 ('dirname', <function posixpath.dirname>),
 ('exists', <function genericpath.exists>),
 ('expanduser', <function posixpath.expanduser>),
 ('expandvars', <function posixpath.expandvars>),
 ('extsep', '.'),
 ('genericpath',
  <module 'genericpath' from '/Users/Theo/anaconda/lib/python3.5/genericpath.py'>),
 ('getatime', <function genericpath.getatime>),
 ('getctime', <function genericpath.getctime>),
 ('getmtime', <function genericpath.getmtime>),
 ('getsize', <function genericpath.getsize>),
 ('isabs', <function posixpath.isabs>),
 ('isdir', <function genericpath.isdir>),
 ('isfile', <function genericpath.isfile>),
 ('islink', <function posixpath.islink>),
 ('ismount', <function posixpath.ismount>),
 ('join', <function posixpath.join>),
 ('lexists', <function posixpath.lexists>),
 ('normcase', <function posixpath.normcase>),
 ('normpath', <function posixpath.normpath>),
 ('os', <module 'os' from '/Users/Theo/anaconda/lib/python3.5/os.py'>),
 ('pardir', '..'),
 ('pathsep', ':'),
 ('realpath', <function posixpath.realpath>),
 ('relpath', <function posixpath.relpath>),
 ('samefile', <function genericpath.samefile>),
 ('sameopenfile', <function genericpath.sameopenfile>),
 ('samestat', <function genericpath.samestat>),
 ('sep', '/'),
 ('split', <function posixpath.split>),
 ('splitdrive', <function posixpath.splitdrive>),
 ('splitext', <function posixpath.splitext>),
 ('stat', <module 'stat' from '/Users/Theo/anaconda/lib/python3.5/stat.py'>),
 ('supports_unicode_filenames', True),
 ('sys', <module 'sys' (built-in)>)]

This is a list of tuples of all public members, where the first itme of each tuple is the name of the attribute and the second is the attribute itself.

To see which attributes are methods and which are not, is just a matter of filtering out the callables. Notice that callable(obj) is a builtin function.


In [49]:
anObj = os.path

[f for f in getmembers(anObj) if not f[0].startswith('_') and callable(f[1])]


Out[49]:
[('abspath', <function posixpath.abspath>),
 ('basename', <function posixpath.basename>),
 ('commonpath', <function posixpath.commonpath>),
 ('commonprefix', <function genericpath.commonprefix>),
 ('dirname', <function posixpath.dirname>),
 ('exists', <function genericpath.exists>),
 ('expanduser', <function posixpath.expanduser>),
 ('expandvars', <function posixpath.expandvars>),
 ('getatime', <function genericpath.getatime>),
 ('getctime', <function genericpath.getctime>),
 ('getmtime', <function genericpath.getmtime>),
 ('getsize', <function genericpath.getsize>),
 ('isabs', <function posixpath.isabs>),
 ('isdir', <function genericpath.isdir>),
 ('isfile', <function genericpath.isfile>),
 ('islink', <function posixpath.islink>),
 ('ismount', <function posixpath.ismount>),
 ('join', <function posixpath.join>),
 ('lexists', <function posixpath.lexists>),
 ('normcase', <function posixpath.normcase>),
 ('normpath', <function posixpath.normpath>),
 ('realpath', <function posixpath.realpath>),
 ('relpath', <function posixpath.relpath>),
 ('samefile', <function genericpath.samefile>),
 ('sameopenfile', <function genericpath.sameopenfile>),
 ('samestat', <function genericpath.samestat>),
 ('split', <function posixpath.split>),
 ('splitdrive', <function posixpath.splitdrive>),
 ('splitext', <function posixpath.splitext>)]

And likewise, we find the attributes that are not callable:


In [51]:
anObj = os.path

[f for f in getmembers(anObj) if not f[0].startswith('_') and not callable(f[1])]


Out[51]:
[('altsep', None),
 ('curdir', '.'),
 ('defpath', ':/bin:/usr/bin'),
 ('devnull', '/dev/null'),
 ('extsep', '.'),
 ('genericpath',
  <module 'genericpath' from '/Users/Theo/anaconda/lib/python3.5/genericpath.py'>),
 ('os', <module 'os' from '/Users/Theo/anaconda/lib/python3.5/os.py'>),
 ('pardir', '..'),
 ('pathsep', ':'),
 ('sep', '/'),
 ('stat', <module 'stat' from '/Users/Theo/anaconda/lib/python3.5/stat.py'>),
 ('supports_unicode_filenames', True),
 ('sys', <module 'sys' (built-in)>)]

How is the signature of a callable objecte (a callable)?

Signature info refers to how objects should be called, what parameters should be passed to them and what return objects they return.

This infor can be retrieved on a per object basis using the Ipython trick with the question mark as described above. But we can also use the function singature(callable) from the module inspect.


In [77]:
from inspect import signature

In [87]:
anObj = os.path

[(f[0], str(signature(f[1]))) for f in getmembers(anObj) if not f[0].startswith('_') and callable(f[1])]


Out[87]:
[('abspath', '(path)'),
 ('basename', '(p)'),
 ('commonpath', '(paths)'),
 ('commonprefix', '(m)'),
 ('dirname', '(p)'),
 ('exists', '(path)'),
 ('expanduser', '(path)'),
 ('expandvars', '(path)'),
 ('getatime', '(filename)'),
 ('getctime', '(filename)'),
 ('getmtime', '(filename)'),
 ('getsize', '(filename)'),
 ('isabs', '(s)'),
 ('isdir', '(s)'),
 ('isfile', '(path)'),
 ('islink', '(path)'),
 ('ismount', '(path)'),
 ('join', '(a, *p)'),
 ('lexists', '(path)'),
 ('normcase', '(s)'),
 ('normpath', '(path)'),
 ('realpath', '(filename)'),
 ('relpath', '(path, start=None)'),
 ('samefile', '(f1, f2)'),
 ('sameopenfile', '(fp1, fp2)'),
 ('samestat', '(s1, s2)'),
 ('split', '(p)'),
 ('splitdrive', '(p)'),
 ('splitext', '(p)')]

However, according to the documentation of module inspect, not all callables are supported by signature(), due to whch this method is not always applicable.

Inspecting the variables in the workshape using globals() and locals()

How to see what variables float in the workspace?

You get a dict of the global active bound identifiers in your workspace by the function globals().


In [76]:
# globals() this can be very long list that you probably don't want to see directly

Likewise, for the local variables you use locals(). However, if you are in a script, that is, at the highest level of the stack, then both 'globals()' and 'locals()' are the same. Only if you call locals() from within a function will the two be different.

An example, shows, how locals() can be called from within a function.


In [60]:
import math

def myfun(x, y, z):
    r = math.sqrt(x**2 + y**2 + z**2)
    print(locals())
    return r

r = myfun(0.3, 0.2, 0.6)


{'y': 0.2, 'x': 0.3, 'r': 0.7, 'z': 0.6}

Inspecting the workspace using the magics %who and %whos (like in Matlab)

The easiest way to inspect the variables (identifiers) that you have added to the workspace is by using jupyters magic fucntions %who and %whos


In [63]:
%who


anObj	 flist	 getmembers	 inspect	 math	 methods	 myfun	 os	 pprint	 
pth	 r	 s	 

In [64]:
%whos


Variable     Type        Data/Info
----------------------------------
anObj        module      <module 'posixpath' from <...>/python3.5/posixpath.py'>
flist        list        n=0
getmembers   function    <function getmembers at 0x10507cf28>
inspect      module      <module 'inspect' from '/<...>ib/python3.5/inspect.py'>
math         module      <module 'math' from '/Use<...>3.5/lib-dynload/math.so'>
methods      function    <function methods at 0x106d57510>
myfun        function    <function myfun at 0x106d8e158>
os           module      <module 'os' from '/Users<...>nda/lib/python3.5/os.py'>
pprint       function    <function pprint at 0x1051596a8>
pth          module      <module 'posixpath' from <...>/python3.5/posixpath.py'>
r            float       0.7
s            str         The quick brown fox

Note that this nice way of matlab like inspection of the workspace is only available in ipyton, not in python. Hence it is available in this jupyter notebook.

Getting the signature (way of calling) of an object can be done in ipython by typing a question mark directly after the object. The info then appears at the bottom of the screen. Press the small x to remove it, if it takes up too much of the screen.