aquiline ascension

published: 2010-10-18
author: Hans Nowak
tags:
python 
stupid-python-tricks 

Stupid Python tricks: Using a custom object as namespace for eval()

The Python documentation for eval says:

eval(expression[, globals[, locals]])

The arguments are a string and optional globals and locals. If provided, globals must be a dictionary. If provided, locals can be any mapping object.

Changed in version 2.4: formerly locals was required to be a dictionary.

That last sentence is important: it means that we can now use any object to look up names, as long as it behaves (somewhat) like a dictionary.

Why would you want to? Well, in my case, I ran into such a situation with magicquery. The idea is that you can enter a Python expression to query the card database. The card object has many properties and methods; I want users to be able to access them like they were local variables. So instead of

card.red and card.type('creature')

you can use:

red and type('creature')

...which is much less cumbersome in the long run. In addition to that, I still want to grant access to commonly used Python globals, like len:

# find all red cards with names longer than 20 characters
red and len(name) > 20

In order to make this happen, the MagicCard objects needed to override __getitem__, so it could be used for lookups like a dictionary. It ended up looking like this:

class MagicCard(object):

    def __getitem__(self, name):
        try:
            return self._data[name]
        except KeyError:
            try:
                return getattr(self, name)
            except AttributeError:
                raise KeyError, name
                
# elsewhere:
for card in cards:
    result = eval(expr, globals(), card)

What happens here? When a name is looked up, we check the object's _data attribute first, which is an actual dictionary. If the name isn't found there, we try looking it up in the object's namespace itself; this will find methods and properties. If that fails as well, we catch the AttributeError and raise a KeyError instead. Why? Because a KeyError will tell eval that the name wasn't found, and it will look in the global dictionary (if available). Any other exception here would interrupt the lookup, so nothing would be looked up in the globals at all.

Granted, there probably won't be many situation where you'll need this... but it's nice that it's possible if you do need it.

blog comments powered by Disqus