[Python-Dev] Adding a builtins parameter to eval(), exec() and import(). (original) (raw)

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