Small Python Interactive Interface Trick

Not too long ago, I helped someone with some research work, and one of the research tools was also used from the Python interactive prompt.
The interface used was handy enough for development: intuitive, readable, short, and useful. Still, for interactive research it wasn’t handy enough.
To improve the script, I decided to use a trick I heard about a long time ago – using __repr__:

class NoParens(object):
    def __init__(self, obj):
        self.obj = obj
    def __call__(self, *args, **kwargs):
        return self.obj(*args, **kwargs)
    def __repr__(self):
        result = self.obj()
        return str(result)

And using it looks like this:

In [2]: import noparens
 
In [3]: def test():
   ...:     return 4**4
   ...:
 
In [4]: t = noparens.NoParens(test)
 
In [5]: t
Out[5]: 256
 
In [6]: t,t,t
Out[6]: (256, 256, 256)

For research consoles that interact with other programs or devices, such as debuggers and sniffers I found this useful, and made work a bit less annoying.

This entry was posted in Programming, Python, Research, Utility Functions and tagged , , , . Bookmark the permalink.

9 Responses to Small Python Interactive Interface Trick

  1. arkon says:

    hey man
    glad you are back to write posts :)
    can you give a more practical example maybe?

  2. lorg says:

    Thanks :)
    The best example would be a debugger:
    Given a single_step() function, use s as a shorthand, and maybe r for run(), and p for print_state().
    Given these, debugging with a Python interactive prompt gets a little bit easier. Especially when you can do:
    s,s,s,p,r

  3. knorby says:

    Why not just use %autocall from the IPython magic commands? You wouldn’t be able to do everything that you can do with your trick (just calling “t” would work the same, but not “t,t,t”), but it would require a whole lot less effort.

  4. lorg says:

    Knorby:
    Two reasons:
    1. I didn’t know about it, thanks :)
    2. Even if I had, I couldn’t have used it. I was using an embedded Python shell, that doesn’t work with IPython. Although integrating it is possible, that work wasn’t done.
    That made using this shell even more painful, because I didn’t have all the fancy IPython features, such as tab-completion. That pain made this trick even more useful than it normally is.

  5. hmarr says:

    Could work nicely as a decorator too:

    def noparens(func):
    class ReprFunc(object):
    def __init__(self, f):
    self.f = f
    def __repr__(self):
    return str(self.f())
    def __call__(self, *args, **kwargs):
    return self.f(*args, **kwargs)
    return ReprFunc(func)

    Usage:

    @noparens
    def foo():
    return 6*3

    >>> foo
    18

  6. lorg says:

    hmarr:
    Nice idea!
    However, to do what you propose you don’t need to change the code of NoParens, you can already use it as is. Any callable object may be used as a decorator.

  7. llr says:

    Is this your script’ purpose as I think? like below:

    >>> def test():
    … return 4**4

    >>> t = test()
    >>> t
    256
    >>>

  8. lorg says:

    llr:
    No, it isn’t.
    In your code snippet, t holds the result of a single call to test().
    However, in the interactive prompt when you do
    >>> x [enter]
    x.__repr__() gets called.
    So, in my version, each time you try to view t, you actually call NoParens.__repr__, and that means a new call to test. I believe this would be clearer if test() returned time.time():

    [python]
    In [2]: import noparens

    In [3]: import time

    In [4]: def test():
    …: return time.time()
    …:

    In [5]: t = noparens.NoParens(test)

    In [6]: t
    Out[6]: 1228124415.74

    In [7]: t
    Out[7]: 1228124416.37
    [/python]

  9. hmarr says:

    lorg:
    Of course! Clearly I didn’t have my head screwed on properly when I wrote that!