exec_code, a class for easing automatic code generation

Keith Amidon (amidon@hpcc103.corp.hp.com)
Mon, 28 Feb 1994 07:24:52 -0800

Someone on the list here recently mentioned that one of the reasons
(s)he would like block delimiters in python was to reduce the effort
required to programmatically generate code for use with exec. Being a
*complete beginner* with python, I became curious about how difficult
it really was with the current syntax, and, as a learning experience,
decided to find out by writing a class to ease code generation. I've
included the class at the end of the message. Here is an extremely
simple example of what the class can do, ripped directly from my
*Python* buffer in emacs:

>>> dir(exec_code)
['__init__', '__str__', 'append_stmt', 'compile', 'dedent', 'execute', 'indent', 'p']
>>> eco = exec_code()
>>> eco.indent_increment = 4
>>> eco.append_stmt("for i in range(0,4):")
>>> eco.indent()
>>> eco.append_stmt("print 'Hi!!!! This is count: ', i")
>>> eco.p()
<exec_code instance> with indent-level 4 and code:
--------------------------------------------------------------
for i in range(0,4):
print 'Hi!!!! This is count: ', i

>>> eco.execute()
Hi!!!! This is count: 0
Hi!!!! This is count: 1
Hi!!!! This is count: 2
Hi!!!! This is count: 3
>>> eco.dedent()
>>> eco.append_stmt("print '---------- ALL DONE!!! ----------'")
>>> eco.exec_code_valid
0
>>> eco.compile()
>>> eco.exec_code_valid
1
>>> eco.p()
<exec_code instance> with indent-level 0 and code:
--------------------------------------------------------------
for i in range(0,4):
print 'Hi!!!! This is count: ', i
print '---------- ALL DONE!!! ----------'
>>> eco.execute()
Hi!!!! This is count: 0
Hi!!!! This is count: 1
Hi!!!! This is count: 2
Hi!!!! This is count: 3
---------- ALL DONE!!! ----------

In reality, my class doesn't do very much, but it may make automatic
code generation appear cleaner, if not done in many fewer statements.
The main reason I've posted the class is to generate discussion on the
topic, and get as much feedback as I can so I can improve my python
programming skills. I'm curious now about what code people are
generating on the fly this way, and how my class could be improved to
make the generation even easier. I didn't do this to fit any
particular need that I have, so I don't really know what would be
useful. Things I have been thinking about adding include:

- The ability to append one exec_code instance to another
using the syntax exec_code_instance1 + exec_code_instance2.
Basically this would just take exec_code_instance2, indent
the entire thing to the current indentation level of
exec_code_instance1, and append it, returning the result.
- The ability to optionally specify the namespace dictionaries
to use in the execute() command. Maybe even the ability to
bind these when a class instance is created using exec_code().
- Add some sequence-type functionality, such that the lines of
code in exec_string can be treated as a sequence of strings,
one line per "sequence item." This would allow modifying
inserted code, using slice operations, and change the
semantics of append_stmt, etc.
- The ability to save a code object to disk as a simple text
module. I could envision some sort of simple, *slow*
line-editor being implemented for the python command line
using a facility like this. It might improve on the current
situation for those without emacs or the readline package,
but I have trouble seriously contemplating extended use of
a line editor. Still, a possibility? Anyone interested?
- ???? Any other reasonable suggestions.

I would appreciate *any* comments on the code at all, especially with
regard to the namespace issues I discussed in the comments for the
execute() method. I'm still trying to figure python out, and I'm not
sure those are correct.

Thanks, Keith Amidon

-----------<snip>------------ exec_code.py ------------<snip>------------
# This class module is a first cut at creating a class to simplify the
# creation of code strings for execution by exec().
#
# by: Keith Amidon -- 2/27/94 -- Use this for whatever you want! :^)

# Usage: read the comments in the code below. I believe it is adequately
# commented.

class exec_code:
###### Data Members ######
#
# exec_string = the string of code that is to be executed.
# exec_code = compiled code from exec_string, to eliminate
# compile time if the exec-code object is to be
# frequently used.
# exec_code_valid = boolean that indicates if exec_code is in sync
# with exec_string.
# indent_level = current amount of indentation. Starts at zero,
# increase/decreases by indent_increment.
# indent_increment = amount to increase/decrease indent_level by.
# Defaults to 1 to make code string as short as
# possible. If you will be doing a lot of
# examination of exec_code objects using str(),
# you may want to set this to your favorite
# indentation level. Don't change this in the
# middle of adding code, as there is currently
# no way to expand/contract the indentation of
# previous lines.

###### Methods ######

# __init__ : Initializes the values of the data members when the
# class is first created
def __init__(self) :
self.exec_string = ""
self.exec_code = compile(self.exec_string,'<string>','exec')
self.exec_code_valid = 1
self.indent_level = 0
self.indent_increment = 1

# __str__ : Return a string representation of the object, for
# nice printing.
def __str__(self) :
return "<exec_code instance> with indent-level " \
+ `self.indent_level` + " and code:\n" \
+ "--------------------------------" \
+ "------------------------------\n" \
+ self.exec_string

# p : Since it is often useful to be able to look at the code
# that is generated interactively, this function provides
# a shorthand for "print str(some_exec_code_instance)", which
# gives a reasonable nice look at the contents of the
# exec_code object.
def p(self) :
print str(self)

# append_stmt : Add a statement at the end of exec_string, with
# the correct indentation. We don't recompile
# exec_string to exec_code after each append_stmt,
# because we want to keep statement insertion as
# inexpensive as possible. However, this makes it
# neccessary to compile the object before executing
# exec_code.
def append_stmt(self, stmt) :
self.exec_code_valid = 0
# I don't know how neccessary the following is, I used for to
# prevent the class from having to import any of the string
# routines. Probably this isn't a problem and performance code
# be improved by using the string functions.
for x in range(0,self.indent_level):
self.exec_string = self.exec_string + ' '
self.exec_string = self.exec_string + stmt + '\n'

# indent : Increase the level of indentation by one.
def indent(self) :
self.indent_level = self.indent_level + self.indent_increment

# dedent : Decrease the level of indentation by one.
def dedent(self) :
self.indent_level = self.indent_level - self.indent_increment

# compile : Compile exec_string into exec_code using the builtin
# compile function. Skip if already in sync.
def compile(self) :
if not self.exec_code_valid :
self.exec_code = compile(self.exec_string,'<string>','exec')
self.exec_code_valid = 1

# execute : Execute exec_code, compiling exec_string to exec_code if
# neccessary. Really, we could just exec exec_string becuase
# exec will do the compile, but this is a good way to keep
# exec_code and exec_string in sync, and reduces overhead if
# the object is executed repeatidly.
#
# NOTE: the symbol name spaces (n.s.) in use will be:
# local = n.s. of the execute() function
# global = global n.s. of the caller
# If it is neccessary to access the local n.s. of the
# caller, or to specify the name spaces within which the
# code segment will execute, simply exec the exec_code
# from within the caller with something like:
#
# some_exec_code_instance.compile()
# exec some_exec_code_instance.exec_code in global,local
#
# Alternatively, use exec_string instead, as exec will
# automatically compile the string. However, if you use
# exec_code, be sure to compile it first, otherwise you
# won't know if it is up to date.
def execute(self) :
if not self.exec_code_valid :
self.exec_code = compile(self.exec_string, '<string>','exec')
exec self.exec_code