Chapter 20. A Macro Language for Kalam

Table of Contents
Executing Python code from Python
Integrating macros with a GUI
Creating a macro API from an application
Conclusion

One thing that separates a run-of-the-mill application from a real tool—one that users will take refuge in day after day—is a good macro facility. Python, which was designed with ease of use in mind, is a natural choice for a macro language. Nine out of ten secretaries would choose Python over the WordPerfect macro language or Visual Basic, given the choice! Isn't it fortunate that we have already begun developing our application in Python?

This chapter deals with integrating a Python based macro facility in Kalam. In the course of this chapter we investigate the execution of Python code while dynamically adding actions and menu items. We also cover granting user access to a predefined API of our application objects.

Of course, the underlying mechanics of a macro facility are not particular to any GUI toolkit. And if you decide to convert your application to C++, you can still embed Python, wrap the API using sip, and allow your users to execute the same macros. We handle powerful stuff in this chapter, and it's well worth the effort.

Executing Python code from Python

There are three basic ways to execute Python code that is not directly (i.e. imported as a module) part of your application. Single statements can be executed with eval(); we already encountered eval() in Chapter 18. Strings that contain more than a single statement of Python code can be executed with exec(), while Python code that is saved in a file can be executed with execfile().

Both eval() and exec() can be fed either plain text strings or pre-compiled Python byte code. You can create blobs of byte code with the compile() function, but you don't have to—it is simply a little more efficient to use bytecode if you execute the same code more than once. The evalfile() function reads in a file and executes the contents, which must be plain text. You cannot feed execfile compiled Python byte code.

Please note that eval(), exec(), execfile() and compile() are the real ginger: this is what your Python interpreter uses to execute your code.

The mere ability to execute random bits of code is quite powerful in itself, but code only becomes truly useful if it no longer exists in a void, but can call other, pre-existing bits of code.

The code we execute with eval(), exec() and execfile() should be brought into relation with the other Python modules that exist in the library, and with the code of our application. Not only that, but preferably also with the state, that is, the variables and objects, of the application that asks eval(), exec() and execfile() to execute the code.

To that end, eval(), exec() and execfile() take two parameters. The first, globals, is a dictionary that represents the global namespace. You can retrieve the global namespace of your application with the function globals(). The global namespace contains all imported classes, built-in functions and all global variables, but you can also construct a restricted global environment dictionary yourself.

The second argument, locals, is a dictionary that represents the local namespace. You can retrieve it with locals(). The locals dictionary contains whatever names are local to the function your application is currently in. You can also create a restricted (or expanded) locals dictionary yourself.

Warning

If you mess about with the globals and locals dictionary, be prepared to encounter what the Python Language Reference calls "undefined behavior". For instance, if you execute a bit of code with an empty locals dictionary, you cannot add new names to the namespace. This means that import won't work, for instance, or even variable assignments. Generally speaking, it is best to simply pass the globals dictionary, which means that the locals dictionary used by the executed code will be a copy of the globals dictionary.

Let's compare these locals and globals from an interactive Python session:

Python 2.0 (#1, Mar  1 2001, 02:42:21)
[GCC 2.95.2 19991024 (release)] on linux2
Type "copyright", "credits" or "license" for more information.
>>> globals()
{'__doc__': None, '__name__': '__main__',
 '__builtins__': <module '__builtin__' (built-in)>}
>>> def f():
...     a=1
...     print "locals: ", locals()
...
>>> globals()
{'f': <function f at 0x8124a94>, '__doc__': None,
 '__name__': '__main__',
 '__builtins__': <module '__builtin__' (built-in)>}
>>> f()
locals:  {'a': 1}
>>>
    

First, we take a look at the contents of the globals dictionary when Python is first started. Then, we define a simple function f, that creates a variable a, which contains the value 1 and which prints the locals dictionary. Retrieving the value of globals shows that f is now part of globals. Running f shows that a is the only member of locals.

By default, the globals and locals arguments of eval(), exec() and execfile() contain the current contents of globals and locals, but you can alter this— for instance, to restrict access to certain application objects.

Playing with eval()

eval() functions as if it executes a single line of code in the Python interpreter: it returns a value that represents the result of the evaluated expression. If the statement you give to eval() raises an exception, the surrounding code gets that exception, too. Playing around with eval() will give you a feeling for what it can do for you.

Figure 20-1. Playing with eval()

boud@calcifer:~/doc/pyqt/ch15 > python
Python 2.0 (#1, Mar  1 2001, 02:42:21)
[GCC 2.95.2 19991024 (release)] on linux2
Type "copyright", "credits" or "license" for more information.
>>> eval
<built-in function eval>
>>> eval("1==1")
1
>>> import string
>>> eval("string.split('bla bla bla')")
['bla', 'bla', 'bla']
>>> eval("string.split('bla bla bla')", {}, {})
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "<string>", line 0, in ?
NameError: There is no variable named 'string'
>>> eval("""from qt import *
... s=QString("bla bla bla")
... print str(s).split()
... """)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "<string>", line 1
    from qt import *
       ^
SyntaxError: invalid syntax
>>>
      

First, we take a look at what "eval" is for a beast. A built-in function. OK, let's try it out. Yes, 1 equals 1 evaluates to 1, which means TRUE - eval neatly returns the result of the code it executes. Next, having imported the string module, we use it to split a string. Here, eval() has access to the global namespace, which means it can access the module we just imported, so string.split() evaluates just fine. However, if we try to evaluate the same expression, but with empty global and local dictionaries, we get a NameError exception - suddenly string isn't known anymore. Trying to evaluate something more complicated, something that is not a single expression that returns a value (even if it's only None) doesn't work at all - which is why exec() exists.

Playing with exec

First, exec is really a statement, not a function, so it doesn't return anything. Just as with eval(), exceptions are propagated outside the code block you execute. You can feed exec a string, a compiled code object or an open file. The file will be parsed until an EOF (end-of-file) occurs, and executed. The same rules hold for the global and local namespace dictionaries as with eval() - but keep in mind that running exec might add new items to those namespaces.

Figure 20-2. Playing with exec

boud@calcifer:~/doc/pyqt/ch15 > python
Python 2.0 (#1, Mar  1 2001, 02:42:21)
[GCC 2.95.2 19991024 (release)] on linux2
Type "copyright", "credits" or "license" for more information.
>>> globals()
{'__doc__': None, '__name__': '__main__',
 '__builtins__': <module '__builtin__' (built-in)>}
>>> code = """
... import qt
... s = qt.QString("bla bla bla")
... print string.split(str(s))
... """
>>> exec code
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "<string>", line 4, in ?
NameError: There is no variable named 'string'
>>> import string
>>> exec code
['bla', 'bla', 'bla']
>>> globals()
{'__doc__': None, 'string': <module 'string' from
'/usr/lib/python2.0/string.pyc'>, '__name__':
 '__main__', '__builtins__': <module '__builtin__'
 (built-in)>, 'qt': <module 'qt' from
'/usr/lib/python2.0/site-packages/qt.py'>,
 'code': '\012import qt\012s = qt.QString("bla bla bla")\012print
string.split(str(s))\012', 's': <qt.QString instance at 0x8278af4>}
>>>
        

First, we create a string that contains the bit of Python we want to execute. Note how it imports the qt module, and how it uses the string module. Executing the code doesn't work: it throws a NameError because string isn't known. Importing string into the global namespace makes it also available to exec, of course. Executing the code string now succeeds, and a quick peek in globals learns us that the module qt has been added.

Playing with execfile()

The execfile() statement is rarely used; after all, it can't do anything beyond what the plain exec statement already does. execfile() functions exactly the same as exec, except that the first argument must be a filename (it doesn't need to be an open file object). Note that execfile() differs from import in that it doesn't create a new module in the global namespace. Note the difference between execfile() and import in the following output:

Figure 20-3. Playing with execfile()

Python 2.0 (#1, Mar  1 2001, 02:42:21)
[GCC 2.95.2 19991024 (release)] on linux2
Type "copyright", "credits" or "license" for more information.
>>> execfile("main.py")
Initializing configuration
{'kalam': <kalamapp.KalamApp instance at 0x825f014>,
 'app': <qt.QApplication instance at 0x814a2a4>, 'args': ['']}
Saving configuration
>>> import main
>>> globals()
{..., 'main': <module 'main' from 'main.py'>, ... }
        

In the middle of all the qt classes the main module of Kalam imports into globals, we find the main module itself, which isn't there if we just execfile main.py.