Embedding a Python Shell in a Python Script

I am huge of advocate of command-line programs and domain specific languages. Back when I worked in statistical genetics, there were many fine programs that ran this way, allowing for easily repeatable analysis*, and easy scripting. Python has some great tools for creating programs driven by mini-languages (such as the cmd module), but they seem to date from a kinder, gentler time when people took what they could get for documentation, and they liked it. Finding simple examples of how they work is tough**. I’m sure in some future post, I’ll tackle this in more depth, but for now I want to focus on a simpler problem: embedding python shells into python scripts.

Should be trivial, right? IDLE exists, and python comes with a bundled interpreter. Searching for help here fails because “embedding a python shell” calls up documentation on how to get a shell in C environments, which is not what I mean at all. How often has it happened that there is some complex script that one wants to introspect, partway through, maybe after some data structures are created or loaded? A typical solution is something like:

if flag:
    import pdb
    pdb.set_trace()

This is fine and dandy, except that it’s not always “debugging” that one wants to do. Sometimes one wants to explore data, or gods forbid, enter new code as an experiment.

I read Rick Muller’s recipe about how to embed a code.InteractiveConsole into a python script, and I thought I could do a little better. The following snippet shows how a --shell command line flag (read using optparse) drops the user into a shell, using IPython if it’s available, or falling back on code.***

#!/usr/bin/python

## filename:  cmdDriven.py
import sys

# some imports, data, and like, so we have something to explore
Info = dict(author="Gregg Lind", blog="Write-Only")
data1 = set(range(100))

def tickle(foo, willing=False):
    if willing:  return "%s just got tickled" %foo
    else: return "I don't think %s would like that so well" % foo

if __name__ == "__main__":
    from optparse import OptionParser
    parser = OptionParser()
    parser.add_option('--shell', action="store_true", default=False, help='get an (ipython) shell to explore the data before output')
    (options, args) = parser.parse_args()

    if options.shell:
        try:
            from IPython.Shell import IPShellEmbed
            ipshell = IPShellEmbed(argv=[''],banner="hello!",exit_msg="Goodbye")
            ipshell()
        except ImportError:
            import code
            # calling this with globals ensures we can see the environment
            shell = code.InteractiveConsole(globals())
            shell.interact()
    else:
        sys.exit("No shell requested")

One of the nice benefits to this coding pattern is that we can do bash scripting using this quite easily:

$ python cmdDriven.py --shell << JUNK
> print Info
> print tickle("bob")
> JUNK
Python 2.5.1 (r251:54863, May 18 2007, 16:56:43)
[GCC 3.4.4 (cygming special, gdc 0.12, using dmd 0.125)] on cygwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> {'blog': 'Write-Only', 'author': 'Gregg Lind'}
>>> I don't think bob would like that so well

Footnotes

* another favorite topic! Cf: Literate programming, Sweave for R-project.

** for cmd: http://www.eskimo.com/~jet/python/examples/cmd/index.html

*** Edit (4/28/09), here is my function I tend to use for this in projects these days:

def prompt(vars, message ):
    #prompt_message = "Welcome!  Useful: G is the graph, DB, C"
    prompt_message = message
    try:
        from IPython.Shell import IPShellEmbed
        ipshell = IPShellEmbed(argv=[''],banner=prompt_message,exit_msg="Goodbye")
        return  ipshell
    except ImportError:
        ## this doesn't quite work right, in that it doesn't go to the right env
        ## so we just fail.
        import code
        import rlcompleter
        import readline
        readline.parse_and_bind("tab: complete")
        # calling this with globals ensures we can see the environment
        print prompt_message
        shell = code.InteractiveConsole(vars)
        return shell.interact

p = prompt(locals(),'')
p()

Note: the ipy interface has been updated in version 0.11 (July 2011) http://writeonly.wordpress.com/2011/08/31/updated-bash-ipython-alias-that-works-for-0-10-and-0-11/ and this code might no longer work.

About these ads

4 Comments on “Embedding a Python Shell in a Python Script”

  1. Paddy3118 says:

    I run progs using `python -i` to drop me into the shell sometimes.

    – Paddy.

  2. This is a great solution!

    import code
    code.InteractiveConsole(globals()).interact()

    Thank you a lot. This is exactly what I was looking for.

  3. Gregg Lind says:

    I use this so often, I wrote a function for it, see revised post.

  4. Embedding a Python Shell in a Python Script | write-only by Gregg Lind


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.