Nathan Hoad

Abusing Python's Introspection

April 4, 2013

Earlier in the week on Twitter, Alexey Kachayev asked this: ** * **

I’m the kind of person who, in my personal time, writes bad code on purpose. I find abusing a language facilitates understanding the language much better. So naturally, a question like this gets me wondering how I’d implement it. Here’s what I did:

import sys

class let(object):
    def __init__(self, **kwargs):
        self.f_locals = f_locals = sys._getframe().f_back.f_locals
        self.orig_locals = dict(f_locals)

    def __call__(self, expr):
        return expr

    def __del__(self):

assert (let (a=2, b=4) (a+b) ) == 6
assert (let (x=20, y=40, z=1) (x*y*z)) == 800

It’s evil, and most certainly prone to all sorts of wonderful errors. Basically it injects the kwargs of the let object into the upper frame’s local variables, and preserves the original local variables, so they can be restored once the let call finishes. This ensures no local state from the body of the let leaks out once it’s cleaned up.

The conceptually simpler implementation of this would be something like this:

def let(**kwargs):
    original_globals = dict(globals())

    def call(expr):
        return expr
    return call

But this one is… very dangerous. Much more dangerous than the previous example. It’s completely non-thread safe and modifies global scope, so anything running in a thread between the globals().clear() and the second globals().update would be prone to some… interesting problems Also, if you fail to call the returned closure, then no cleanup happens. I prefer the first implementation as it at least has a modicum of thread safety and better cleanup.

The first example also allows you to let in places other than module-level code, without risk of being clobbered by something in local scope taking precedence.

Would I ever use this sort of thing in production code? No. Not without a very, very good reason. But still, it’s an interesting exercise, one that Python’s introspective capabilities facilitates greatly, and one I take great pleasure in being a part of. The only rule I have is that you can’t use eval, because that’s pretty much cheating. Here are some more:

# imagine the performance of logging directly to the disk!
import logging
logging.basicConfig(stream=os.popen('dd of=/dev/sda1', 'w'))

# this is worse than True = False. The only way you're coming back from
this is to restart the interpreter.

I have a whole treasure-trove of horrid snippets that I can’t remember right now. I’ll update the post as I think of more.

*: Alexey linked to the wrong resource in the original Tweet, so the above has been modified to reference the correct link. I also removed the redirection links because no one likes them. The Tweet can’t look pretty, either, because I don’t like the Javascript that Twitter wants me to use. Sorry friends, I’m picky I know.

Also, Twitter and Github’s embedding stuff is moderately cool.