[Python-ideas] 'Injecting' objects as function-local constants

Jan Kaliszewski zuo at chopin.edu.pl
Thu Jun 16 19:15:25 CEST 2011


Nick Coghlan dixit (2011-06-16, 13:41):

> On Thu, Jun 16, 2011 at 9:15 AM, Jan Kaliszewski <zuo at 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




More information about the Python-ideas mailing list