Ok, let's take a look at how my typical Python coding session starts off:


  1. Write a new awesome Python script and save it as "justdoit.py" all 1000 lines of it
  2. Run it to feel the power: "python justdoit.py"
  3. Groan as it crashes with a ValueError at justdoit.py:45
  4. Pull up the source and line -- hmm, what am I getting back from that library call?  Must not be what I thought!
  5. Add a print statement and then run it again and feel the power!
  6. Don't pass go, don't collect $200, go directly to step 3


That gets tedious after a while, particularly when the script is connecting to a remote device, making more than a handful of calls and takes a full 30 seconds to get to that crash site.  (30 seconds is just long enough to be a drag but really not long enough to justify checking the headlines on hacker news...)


After too many iterations of this (define stupid?), I admit defeat.... I need to try something different.

IPython to the rescue


In it's simplest form, you can use IPython as a shell just like python.  That gives you some key new features above and beyond the standard python shell:


  • tab completion - hit [tab] after typing some text, it will automatically complete matching text and display options.  This works for variables, functions, methods, classes, you name it
  • history - your history is saved, even if you exit and restart.  You start to rely on history when you just want to play around with a few new functions.


Ok, so how does this help with my debugging session?  Just put this little gem where the problem is and I can drop into an IPython session -- with full context of variables for inspection:


from IPython import embed


Let's see this in action.  Take the code (from the FlyScript Python SDK examples/profiler/top-ports.py):


from rvbd.profiler import *
from rvbd.profiler.utils.app import ProfilerApp
from rvbd.profiler.filters import TimeFilter, TrafficFilter
import pprint

def main(app):
    # Create and run a traffic summary report of all server ports in use
    # by hosts in 10/8
    report = TrafficSummaryReport(app.profiler)

    # Run the report
    report.run( groupby = app.profiler.groupbys.port,
        columns = [app.profiler.columns.key.protoport_name,
                    app.profiler.columns.value.avg_bytes] )

    # Retrieve and print data
    data = report.get_data()

    from IPython import embed

    print "Report data has %d rows" % len(data)



See lines 19 and 20?  This is where the magic occurs.  When I run this script, it will connect to my Profiler, create and run a traffic report, return the data and the drop into an IPython shell:


--> python /tmp/test.py tm08-1.lab.nbttech.com -u admin -p admin
Python 2.7.3 (default, Oct 22 2012, 06:12:32)
Type "copyright", "credits" or "license" for more information.

IPython 0.13 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.



At this point I'm in the IPython shell right after the 'report.get_data()' line.  Now let's examine some of the local variables:


>>> print report
<rvbd.profiler.report.TrafficSummaryReport object at 0x10532df90>

>>> print len(data)

>>> data[0]
['snmp', 9327.5625]


Note that I can even call functions and methods:


>>> print report.get_legend()[0]
<rvbd.profiler._types.Column(id=19, key=protoport_name, iskey=True label=Port Name)>


I can do whatever I like in this session, including modifying variables.  When I exit, the script continues, as if the IPython session never happened (any variable changes are discarded):


>>> exit

Report data has 114 rows


This is really just scratching the surface of what IPython can do.  If you like what you've seen so far, I leave you with a little teaser: IPython Notebooks