
Nick Coghlan wrote:
On Fri, Jun 17, 2011 at 3:37 PM, Steven D'Aprano <steve@pearwood.info> wrote:
Here's a quick and dirty version that comes close to the spirit of inject, as I see it. Thanks to Alex Light's earlier version. [code snipped]
Sorry, I meant to point out why this was a bad idea when Alex first posted it.
The __globals__ reference on a function object refers to the globals of the module where the function is defined. Modify the contents of that dictionary and you modify the contents of that module. So this "injection" approach not only affects the function being decorated, but every other function in the module. Thread safety is completely non-existent and cannot be handled locally within the decorated function.
I believe that you may have missed that the _modifyGlobals context manager makes a copy of globals before modifying it. But even if you are correct, and the implementation as given is broken, I had written: [quote] Unfortunately, this proof-of-concept inject function DOESN'T ACTUALLY INJECT INTO LOCALS [emphasis added], hence the "import builtins" work-around. But it demonstrates the intent, and the API. [end quote] There was no intention for this to be the working implementation, just to demonstrate the API. As I described earlier, "close to the spirit of inject". I agree with much of the rest of your post (snipped for brevity), with a few additional points below:
The only question is how to tell the compiler about it, and there are three main options for that:
Four actually.
1. Embedded in the function header, modelled on the handling of keyword-only arguments:
def example(arg, **, cache=set(), invocations=0):
Cons: look like part of the argument namespace (when they really aren't), no mnemonic to assist new users in remembering what they're for, no open questions
Additional con: you can only inject such locals at the time you write the function. Cannot take an existing function and make a runtime modification of it. This is, essentially, a compiler directive masquerading as function parameters.
2. Inside the function as a new statement type (bikeshed colour options: @def, @shared, shared)
Pros: implementation detail of shared state is hidden inside the function where it belongs, keyword choice can provide a good mnemonic for functionality
This proposal shouldn't be just about shared state. That is just one use-case out of a number, and not all use-cases should be hidden.
Cons: needs new style rules on appropriate placements of @def/shared statements (similar to nonlocal and global), use of containing namespace for execution may be surprising
Additional cons: looks too much like a decorator, particularly if the function contains an inner function; also looks too much like a function definition. Can only be performed when the function is written, and cannot be applied to existing functions. This too is a compiler directive, this time masquerading as a decorator.
Open Questions: whether to allow only one line with a tuple of assignments or multiple lines, whether to allow simple assignments only or any simple non-flow control statement
Whether the @def line must appear at the start of the function (before or after the docstring), or like globals, can it appear anywhere in the function?
3. After the decorators and before the function definition (bikeshed colour options: @def, @inject, @shared)
This too is a compiler directive masquerading as a decorator. It too suffers from much the same cons as putting @def inside the function body. You have missed a fourth option, which I have been championing: make inject an ordinary function, available from the functools module. The *implementation* of inject almost certainly will require support from the compiler, but that doesn't mean the interface should! Pros: - "Inject" is the obvious name, because that's what it does: inject the given keyword arguments into a (copy of a) function as locals. - Not a compiler directive, but an ordinary function that operates at runtime like any other function. - Hence it works like ordinary decorators. - Doesn't require a new keyword or new syntax. - Can be applied to any Python function at any time, not just when the function is written. Cons: - The implementation will require unsupported bytecode hacks, or compiler support, but so will any other solution. This is only a negative when compared to the alternative "do nothing". - Some people may disagree that "inject" is the obvious name. There may still be room for bikeshedding. Open questions: - Should injected locals go directly into the locals, as if executed in the body of the function, or into a new "shared/injected locals" namespace as suggested by Nick? -- Steven