[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