A blog on namespaces and, simultaneously, a tribute to the late George Harrison. I do this all without a safety net, you know.
As the Zen of Python so, er, neatly puts it; namespaces are indeed one honking great idea. Though one meaning of the verb “to honk” in Brit/Oz circles is “to vomit”, and I’m sure Tim Peters didn’t actually intend that. Namespaces are Interesting, when they’re not being tricky – I’m sure most beginners at Python have been caught at least once by the name-resolution rules in methods. Anyway, yet again I can use a pondering on a Python point to blatantly plug Quasi, where namespaces have led to some intriguing code.
Consider a Python interpreter, into which one types some code, such as:
while True: print "Repeat after me; endless loops are bad"
There are two parallel programs operating here; the shell, which receives keystrokes or lines of input, and the actual interpreter, that runs the resulting parsed-and-compiled code. Both are the same process, they share process-level attributes like the current working directory. However, depending on how the shell’s been written, it and the interpreter may not share namespaces.
The exec keyword allows passing of dicts for the local and global namespaces in which the code object is to be executed. In Quasi, these are separate namespaces, so a command like:
x = 1
will define an x that’s in the interpreter namespace, but not the shell’s. This has some interesting practical consequences. Firstly, when exceptions are raised in the executed code, control returns back to the shell, which doesn’t have access to the variables that may have caused it, and therefore can’t do much about it, other than catch the exceptions and show a traceback. This isn’t, in practice, a problem in any context other than that of someone trying to run a debugger on a shell… but if people will insist on such mental convolutions, that’s their own lookout.
Secondly, and more Interestingly, any shell commands (that is, stuff that’s executed directly by the shell) don’t have access to that namespace. You can’t, for example, hack in a cd command to the shell and allow it to do stuff like
myDir = "/mnt/extended/downloads" cd myDir
Instead, you must implement the cd command so that it passes some equivalent command to the Python interpreter, where myDir is accessible. There are some nice advantages to this; the shell variables don’t pollute the interpreter namespace; the interpreter and the shell can have different sets of loaded modules, and so forth.
This has led to a Design Principle for Quasi that’s been sort of implicit for a while, but which was thrown into sharper focus when cd, pwd, pushd and popd were added. It is that: with very minor exceptions, Quasi does everything by turning the commands typed in into Python source code that’s then compiled and passed to the interpreter.
Consider that most canonical of Quasi examples:
myFiles = `os ls *.py`
This gets translated to the poetic:
myFiles = quasi.QuasiOs(True).execute('os',"ls *.py",())
Just rolls off the tongue, doesn’t it? It gets even more involved if we do variable substitution:
os cp $myFiles backup
quasi.QuasiOs(True).execute('os',"cp %s backup",[(myFiles,'i')])
Note: that’s a string, which is then compiled (using the CommandCompiler class from codeop) and finally passed to exec.
So Quasi is, in fact, an interpreter for an interpreter, which interprets typed commands and compiles them into Python source which is then compiled into Python bytecodes and executed by a Python interpreter which is also the same interpreter that’s executing the whole thing.
And some people wonder why programming is so fascinating…