[Python-ideas] 'Injecting' objects as function-local constants
Steven D'Aprano
steve at pearwood.info
Fri Jun 17 14:12:58 CEST 2011
Nick Coghlan wrote:
> On Fri, Jun 17, 2011 at 3:37 PM, Steven D'Aprano <steve at 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
More information about the Python-ideas
mailing list