
Nick Coghlan dixit (2011-06-16, 13:41):
On Thu, Jun 16, 2011 at 9:15 AM, Jan Kaliszewski <zuo@chopin.edu.pl> wrote:
One question is whether it is technically possible to avoid introducing a new keyword (e.g. staticlocal) explicitly marking injected locals. Using such a keyword would be redundant from user point of view and non-DRY:
There has to be *something* that tells the compiler to generate different bytecode (either a local lookup, a closure lookup or something new). This lands in the same category as "nonlocal" and "global" (and no, a "magic" decorator that the compiler recognises is not a reasonable alternative).
That's why I quite liked the @def idea - it would just define a sequence of simple statements that the compiler will run at function *definition* time that is then used to preseed the local namespace at function *call* time (remember, it is intended to be a mnemonic for "at definition time" and the use of '@' also reflects the fact that this code would run just before function decorators are executed). Just like a class body, the @def code itself would be thrown away and only the resulting preseeded locals information would be retained. To allow rebinding to work correctly, this shared state could be implemented via cell variables rather than ordinary locals.
Possible implementation sketch:
Compile time: - @def statements are compiled in the context of the containing scope and stored on a new ASDL sequence attribute in the Function AST - symtable analysis notes explicitly which names are bound in the @def statements and this information is stored on the code object - code generation produces a cell lookup for any names bound in @def statements (even if they are also assigned as ordinary locals) - Raises a SyntaxError if there is a conflict between parameter names and names bound in @def statements
Definition time: - @def statements are executed as a suite in the context of the containing scope but using a *copy* of the locals (so the containing scope is not modified) - names bound in the @def statements (as noted on the code object) are linked up to the appropriate cells on the function object
Execution time: - Nothing special. The code is executed and references the cell variables precisely as if they came from a closure.
An API could be provided in functools to provide a clean way to view (and perhaps modify) the contents of the cells from outside the function. And yes, I'm aware this blurs the line even further between functions and classes, but the core difference between "a specific algorithm with some persistent state" and "persistent state with optional associated algorithms" remains intact.
And, to repeat the example of how it would look in practice:
def do_and_remember(val, verbose=False): @def mem=collections.Counter() # Algorithm that calculates result given val mem[val] += 1 if verbose: print('Done {} times for {!r}'.format(_mem[val], val)) return result
It is not less 'magic' than what I proposed as variant #3. And, in fact, it is almost the same -- the only important difference is the place: imho placing it *before* definition better emphasizes that the binding is an *early* one. @inject(mem=collections.Counter(), MAX_MEM=1000) def do_and_remember(val, verbose=False): or even (to stress that it is a language syntax construct: @inject mem=collections.Counter(), MAX_MEM=1000 def do_and_remember(val, verbose=False): or: @inject collections.Counter() as mem, 1000 as MAX_MEM def do_and_remember(val, verbose=False): or something similar... Also, such placement is imho more appropriate for @-that-starts-a-line- -syntax (because it would *resemble* decorating syntax anyway -- and what's wrong with that?) + we avoid misleading clusters such as: def do_something(func): @def mem=colletions.Counter # <- looks a bit like another @wraps(func) # decorator for wrapper_func() @my_decorator(mem) def wrapper_func(): ... Regards. *j