[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