From: tim_one at email.msn.com (Tim Peters)
Date: Thu, 15 Apr 1999 01:36:38 GMT
Subject: reval builtin
In-Reply-To: <7f36m6$koq@ds2.acs.ucalgary.ca>
References: <7f36m6$koq@ds2.acs.ucalgary.ca>
Message-ID: <001701be86e0$68574a60$589e2299@tim>
Content-Length: 2254
X-UID: 657                                                  

[Neil Schemenauer]
> It would be nice to have an "reval" builtin that would only evaluate
> literals.  That would make building things like config files safe and
> easy.  I have two ideas on how to accomplish this:
>
> 	1. Create a new start symbol "reval_input" in the Grammar/Grammar
> 	and add a "builtin_reval" function in Python/bltinmodule.c. ...
>
> 	2. Use something like lex and yacc to create an extension module
> 	that does the Right Thing(TM).  ...
>
> Perhaps some guru can explain an easy way to accomplish this and same me
> some time.

The /F-bot already did -- this is Python, and nothing is *that* hard <wink>.
Even  builtins are looked up dynamically at runtime, so all that's required
is to take away eval's normal context.  As a one-liner:

def reval(string):
    return eval(string, {"__builtins__": {}})

This tells eval to use the dict {"__builtins__": {}} for both local and
global lookups.   "__builtins__" is the only key, so is the only name that
*can* be looked up successfully.  It's a semi-magical key that Python
accesses internally when all other attempts to resolve a name fail, and in
this case its value is an empty dict, so that makes Python's last-resort
name-lookup fail too.

>>> reval('1+2')
3
>>> reval('int("3")')
Traceback (innermost last):
  File "<pyshell#11>", line 1, in ?
    reval('int("3")')
  File "<pyshell#9>", line 2, in reval
    return eval(string, {"__builtins__": {}})
  File "<string>", line 0, in ?
NameError: int
>>>

Caution:  it does not work to pass eval an empty dict:

>>> eval('int("3")', {})
3
>>>

That may be obscure <snort>.  The reason is this:

>>> empty = {}
>>> eval('int("3")', empty)
3
>>> empty.keys()
['__builtins__']
>>>

That is, if the dict you pass to eval (or exec) doesn't have a
"__builtins__" key, Python inserts one for you, with the same value as the
current module's __builtins__.  This is to make the normal use of eval/exec
easier.  So to stop Python cold, you have to supply your own "__builtins__"
key.

If you're determined to allow only literals (I don't see any harm in
allowing e.g. 1+3), probably easiest to feed the string to the std parser
module, then crawl over the AST looking for things to complain about.

syntactic-cleansing-ly y'rs  - tim






