[Python-Dev] Adding a builtins parameter to eval(), exec() and __import__().
Mark Shannon
mark at hotpy.org
Fri Mar 9 13:25:03 CET 2012
Nick Coghlan wrote:
> On Fri, Mar 9, 2012 at 6:19 PM, Mark Shannon <mark at hotpy.org> wrote:
>> The Python API would be changed, but in a backwards compatible way.
>> exec, eval and __import__ would all gain an optional (keyword-only?)
>> "builtins" parameter.
>
> No, some APIs effectively define *protocols*. For such APIs, *adding*
> parameters is almost of comparable import to taking them away, because
> they require that other APIs modelled on the prototype also change. In
> this case, not only exec() has to change, but eval, __import__,
> probably runpy, function creation, eventually any third party APIs for
> code execution, etc, etc.
>
> Adding a new parameter to exec is a change with serious implications,
> and utterly unnecessary, since the API part is already covered by
> setting __builtins__ in the passed in globals namespace (which is
> appropriately awkward to advise people that they're doing something
> strange with potentially unintended consequences or surprising
> limitations).
It is the implementation that interests me.
Implementing the (locals, globals, builtins) triple as a single object
has advantages both in terms of internal consistency and efficiency.
I just thought to expose this to the user.
I am now persuaded that I don't want to expose anything :)
>
> That said, binding a reference to the builtin *early* (for example, at
> function definition time or when a new invocation of the eval loop
> first fires up) may be a reasonable idea, but you don't have to change
> the user facing API to explore that option - it works just as well
> with "__builtins__" as an optional value in the existing global
> namespace.
OK. So, how about this:
(builtins refers to the dict used for variable lookup, not the module)
New eval pseudocode
eval(code, globals, locals):
triple = (locals, globals, globals["__builtins__"])
return eval_internal(triple)
Similarly for exec, __import__ and runpy.
That way the (IMO clumsy) builtins = globals["__builtins__"]
only happens at a few known locations.
It should then be clear where all code gets its namespaces from.
Namespaces should be inherited as follows:
frame:
function scope: globals and builtins from function, locals from parameters.
module scope: globals and builtins from module, locals == globals.
in eval, exec, or runpy: all explicit.
function: globals and builtins from module (no locals)
module: globals and builtins from import (no locals)
import: explicitly from __import__() or
implicitly from current frame in an import statement.
For frame and function, free and cell (nonlocal) variables would be
unchanged.
On entry the namespaces will be {}, {}, sys.modules['builtins'].__dict__
This is pretty much what happens anyway,
except that where code gets its builtins from is now well defined.
Cheers,
Mark.
More information about the Python-Dev
mailing list