2. Inspection#

Everything in Python is an object. You can inspect objects! Objects usually contain help information, and you can see what’s available, and often can even find the source all through inspection.

2.1. Inspecting#

You can inspect objects. There are lots of ways.

  • In a Jupyter notebook, use object.<tab> to bring up completions, shift tab for help.

  • You can use dir(object) to see all attributes (more or less)

  • You can use help(object) or object? (IPython only) to see help

  • You can import inspect and use the tools there

  • You can install the rich library and use rich.inspect()

def f(x: float) -> float:
    """I am a square!"""
    return x**2

The help of an object includes its signature and its docstring:

help(f)
Help on function f in module __main__:

f(x: float) -> float
    I am a square!

You can see a list of methods (or use <tab> in iPython or the Python REPL, but underscored methods often require you start by typing an underscore first):

dir(f)
['__annotations__',
 '__builtins__',
 '__call__',
 '__class__',
 '__closure__',
 '__code__',
 '__defaults__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__get__',
 '__getattribute__',
 '__globals__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__kwdefaults__',
 '__le__',
 '__lt__',
 '__module__',
 '__name__',
 '__ne__',
 '__new__',
 '__qualname__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__']

The inspect module is a built-in module that can provide a lot of other information:

import inspect

print(inspect.getsourcefile(f))
print()
print(inspect.getsource(f))
# WARNING! THIS DOES NOT ALWAYS WORK!
/tmp/ipykernel_2705/260819024.py

def f(x: float) -> float:
    """I am a square!"""
    return x**2

WARNING: You cannot always see the source of a function, so this is a user trick, not one to use in a library!

Python does a three stage procedure when interpreting. It converts source to bytecode (pyc files), then runs the bytecode in the interpreter. When loading a file that has been run before (or came from a wheel, more on that later), it only loads the bytecode if the source hasn’t changed - the source is not re-parsed. So inspect works by looking up the original file location. But you can delete the original file and run from bytecode only! Don’t do that, but you can. Also, you can run from a zip file, and the original file might not be openable. And, finally, when running live in a REPL, there may not be a source (it works in IPython for us, though).

import rich

rich.inspect(f)
╭─────────────── <function f at 0x7fa7916fd090> ───────────────╮
 def f(x: float) -> float:                                    
                                                              
 I am a square!                                               
                                                              
 36 attribute(s) not shown. Run inspect(inspect) for options. 
╰──────────────────────────────────────────────────────────────╯
rich.inspect(3)
╭────── <class 'int'> ───────╮
 int([x]) -> integer        
 int(x, base=10) -> integer 
                            
 ╭────────────────────────╮ 
  3                       
 ╰────────────────────────╯ 
                            
 denominator = 1            
        imag = 0            
   numerator = 3            
        real = 3            
╰────────────────────────────╯

Try adding different keyword arguments to rich.inspect. Shift-tab in IPython to see options. methods=True on the int, for example.