Description
I like the closure
argument to exec() and eval() added to 3.11.
But, ... I have some complaints about it as it stands at present.
The closure argument specifies a closure–a tuple of cellvars. It’s only valid when the object is a code object containing free variables. The length of the tuple must exactly match the number of free variables referenced by the code object.
Which free variables?? In the exec() code, or in the frame where exec() is called from?
Where do I get or make cellvars?
In what order do the cellvars go?
Without answering these questions, the new feature will be useless to a programmer.
The answers, as well as I can figure out, are:
- The whole business only applies if exec() is executing a code object compiled from a target which is function or lambda.
- EDIT: The purpose of the feature is to allow the executed code to assign or delete a free variable (which must be declared nonlocal), or get the value of a free variable.
- This code object is in target.code.
- The names of the free variables are in target.code.co_freevars.
- The appropriate closure argument is in target.closure. This has the cellvars corresponding to the free variables named in target.code.co_freevars, and in the same order.
- You can also use a different closure from a different target, provided it was compiled in the same scope, and contains the same variable names (even if they appear in a different order)
- The order in which free variable names appear is fixed in the enclosing scope, so different targets will have their free variable names in the same order.
- If the code binds, rebinds, or deletes one of the free variables (which must be declared as nonlocal in the code), that change will show up in that variable in the calling scope.
- There is no way in Python to create a cellvar other than the above. In the future, if some means is provided to create a cellvar for a free variable, then that cellvar will be tied to that variable in the scope in which it was created.
Suggested enhancement:
-
I propose a new builtin function called closure(). This will return a dict of cellvars for all free variables in the current scope. The same dict can be used by exec() with different code objects compiled in the scope.
-
closure() may be called as closure(names, [name, ]...), which will return a dict for just those variables (which must be free variables in the current scope).
-
exec() will also accept a closure argument which is a dict of variable names to cellvar objects. This may include names which are not free variables in the code object being executed, but it must include all names which are free variables. Any stores or deletes performed in the executed code will show up in the cellvar values. If they reflect the actual free variables in the scope, the changes will show up in the variables in the calling scope.
-
Why not provide a closure to eval() also? eval() can already evaluate an expression with free variables. However, eval() of an assignment expression doesn't work as expected. The assignment is made in the locals dict given to eval(). Passing a closure would allow the assignment to be made to a free variable.
-
Provide methods on cellvar objects to bind, rebind, or delete the cell_contents in-place. This will be visible in the free variable which the cellvar corresponds to. This would be more convenient than performing the corresponding exec().