Monday, March 18, 2013

Python printval

After reading about Python inspect, I realized that a lot of information is available to a function being called. One can even look downwards into the scope of the caller. It occurred to me that this could be useful for debugging; for quick and dirty debugging I sometimes use the "print values to stdout and see what's wrong" technique. I came up with Printval -- quick Python debugging that can also print expressions and locals.
>>> from printval import printval
>>> a = 4
>>> b = 5

>>> printval| a
a is 4

>>> printval| b
b is 5

>>> print('3*a*b is '+str(3*a*b))
3*a*b is 60

>>> printval| 3*a*b
3*a*b is 60

See the last example -- saves typing!

Here's all of printval:

import inspect, itertools
class PythonPrintVal(object):
    def _output(self, name, val):
        # expands a struct to one level.
        print('printval| '+name+' is '+repr(val))
        if name=='locals()':
            for key in val:
                if not key.startswith('_'):
                    self._output(key, val[key])
        elif ' object at 0x' in repr(val):
            for propname in dir(val):
                if not propname.startswith('_'):
                    sval = repr(val.__getattribute__(propname))
                    print(' \t\t.'+propname+'  =  '+sval)

    def __or__(self, val):
        # look in the source code for the text
        fback = inspect.currentframe().f_back
            with open(fback.f_code.co_filename, 'r') as srcfile:
                line = next(itertools.islice(srcfile, fback.f_lineno-1, fback.f_lineno))
                self._output(line.replace('printval|','',1).strip(), val)
        except (StopIteration, IOError):
            return self._output('?',val)

printval = PythonPrintVal()
#really, that's all the code there is!

It will also enumerate through the fields of a class. If you use printval| locals(), you get a more-nicely formatted representation of all locals in scope.

(In an earlier version, I used the syntax printval.a, so I could create chains like printval.a.b. This method doesn't require looking at source code -- the printval object knows the string 'a' from the parameter passed to __getattribute__, and retrieves its value from the scope of the caller. As a benefit this could work in compiled/frozen apps. The downside is that this doesn't allow expressions like printval|a+4)

It turns out that other people have had similar ideas; there are modules for ScopeFormatter, Say, and Show.

For ultimate scratchpad-ability:

  1. The LnzScript editor allows you to run untitled scripts. Modify the file so that it runs Python instead.
  2. Add a keybinding where Ctrl+\ inserts the text "printval| " into the editor.
  3. You now have a nifty scratchpad for running math expressions and testing Python code.