I need a way to "inject" names into a function from an outer code block, so they are accessible locally and they don't need to be specifically handled by the function's code (defined as function parameters, loaded from *args
etc.)
The simplified scenario: providing a framework within which the users are able to define (with as little syntax as possible) custom functions to manipulate other objects of the framework (which are not necessarily global
).
Ideally, the user defines
def user_func():
Mouse.eat(Cheese)
if Cat.find(Mouse):
Cat.happy += 1
Here Cat
, Mouse
and Cheese
are framework objects that, for good reasons, cannot be bounded to the global namespace.
I want to write a wrapper for this function to behave like this:
def framework_wrap(user_func):
# this is a framework internal and has name bindings to Cat, Mouse and Cheese
def f():
inject(user_func, {'Cat': Cat, 'Mouse': Mouse, 'Cheese': Cheese})
user_func()
return f
Then this wrapper could be applied to all user-defined functions (as a decorator, by the user himself or automatically, although I plan to use a metaclass).
@framework_wrap
def user_func():
I am aware of the Python 3's nonlocal
keyword, but I still consider ugly (from the framework's user perspective) to add an additional line:
nonlocal Cat, Mouse, Cheese
and to worry about adding every object he needs to this line.
Any suggestion is greatly appreciated.
The more I mess around with the stack, the more I wish I hadn't. Don't hack globals to do what you want. Hack bytecode instead. There's two ways that I can think of to do this.
1) Add cells wrapping the references that you want into
f.func_closure
. You have to reassemble the bytecode of the function to useLOAD_DEREF
instead ofLOAD_GLOBAL
and generate a cell for each value. You then pass a tuple of the cells and the new code object totypes.FunctionType
and get a function with the appropriate bindings. Different copies of the function can have different local bindings so it should be as thread safe as you want to make it.2) Add arguments for your new locals at the end of the functions argument list. Replace appropriate occurrences of
LOAD_GLOBAL
withLOAD_FAST
. Then construct a new function by usingtypes.FunctionType
and passing in the new code object and a tuple of the bindings that you want as the default option. This is limited in the sense that python limits function arguments to 255 and it can't be used on functions that use variable arguments. None the less it struck me as the more challenging of the two so that's the one that I implemented (plus there's other stuff that can be done with this one). Again, you can either make different copies of the function with different bindings or call the function with the bindings that you want from each call location. So it too can be as thread safe as you want to make it.Note that a whole branch of this code (that relating to
EXTENDED_ARG
) is untested but that for common cases, it seems to be pretty solid. I'll be hacking on it and am currently writing some code to validate the output. Then (when I get around to it) I'll run it against the whole standard library and fix any bugs.I'll also probably be implementing the first option as well.
Edited answer -- restores namespace dict after calling
user_func()
Tested using Python 2.7.5 and 3.3.2
File framework.py:
Example usage:
Output:
If your application is strictly Python 3, I don't see how using Python 3's
nonlocal
is any uglier than writing a decorator to manipulate function's local namespace. I say give thenonlocal
solution a try or rethink this strategy.Sounds like you maybe want to be using
exec code in dict
, wherecode
is the user's function anddict
is a dictionary you provide which canDocs for exec: http://docs.python.org/reference/simple_stmts.html#the-exec-statement
However, I'm pretty sure that this would only work if the user's code is being brought in as a string and you need to exec it. If the function is already compiled, it will already have its global bindings set. So doing something like
exec "user_func(*args)" in framework_dict
won't work, becauseuser_func
's globals are already set to the module in which it was defined.Since
func_globals
is readonly, I think you'll have to do something like what martineau suggests in order to modify the function globals.I think it likely (unless you're doing something unprecedentedly awesome, or I'm missing some critical subtlety) that you probably would be better off putting your framework objects into a module, and then have the user code import that module. Module variables can be reassigned to or mutated or accessed quite readily by code that's been defined outside of that module, once the module has been
import
ed.I think this would be better for code readibility also, because
user_func
will end up having explicit namespacing forCat
,Dog
, etc. rather than readers unfamiliar with your framework having to wonder where they came from. E.G.animal_farm.Mouse.eat(animal_farm.Cheese)
, or maybe lines likeIf you are doing something unprecedently awesome, I think you'll need to use the C API to pass arguments to a code object. It looks like the function PyEval_EvalCodeEx is the one you want.