[Python-Dev] once [was: Simple Switch statementZ]

Guido van Rossum guido at python.org
Wed Jun 28 18:40:15 CEST 2006

On 6/28/06, Jim Jewett <jimjjewett at gmail.com> wrote:
> On 6/25/06, Ka-Ping Yee <python-dev at zesty.ca> wrote:
> >     def f(x):
> >         def g(y):
> >             return y + once x
> >         return g
> > Does "once" mean not really once here, but "once for each new function
> > object that's created for g"?
> Until today, it hadn't really occurred to me that once could mean once
> per module load rather than once per defining scope.

Funny. Until today (in a different post) it hadn't occurred to me that
the proponents of "first-use" switch evaluation were talking about
first-use within a function object. I guess the words are ambiguous.

I'm not really a proponent of "once-per-module-scope" semantics in
either case, so I'll gladly drop this issue.

> I suppose that
> is reasonable if the values really are constant, but most of the
> concerns are about what to do when this assumption is violated.  It
> does add a bit of funny flow-control, though.
>     def f():
>         def g():
>             def h():
>                 once x # or switch
> Normally, there wouldn't be any need to even look inside g (let alone
> h) at module load time, because the definition of f was run, but the
> definitions of g and h were not.  With module-level once, x is
> implicitly a module-level variable despite the nesting.
> Guido:
> > He specifically wants the latter semantics because it solves the
> > problem of binding the value of a loop control variable in an outer
> > scope:
> Not really.  To solve the loop control problem (where the "constant"
> is certainly not a run-time constant), a once variable also has to be
> eagerly evaluated.  (function definition time?)
> Nick suggested using once to delay computation of expensive defaults.
> This means that even if every generated function has its own once
> variable, none of those variables would be bound to any specific value
> until they are called -- by which time the loop variable may well be
> rebound.

Hm. We couldn't use this interpretation of 'once' to capture the value
of a loop variable in a nested function. Recall the typical example;
the goal is to return a list of argument-less functions that return 0,
1, 2, corresponding to their position in the list. The naive approach

  def index_functions(n):
    return [(lambda: i) for i in range(n)]

This returns a list of 10 functions that each return the final
variable of 'i', i.e. 9.

The current fix is

  def index_functions(n):
    return [(lambda i=i: i) for i in range(n)]

which works but has the disadvantage of returning a list of functions
of 0 or 1 argument

I believe at least one poster has pointed out that 'once' (if defined
suitably) could be used as a better way to do this:

  def index_functions(n):
    return [(lambda: once i) for i in range(n)]

But delaying the evaluation of the once argument until the function is
called would break this, since none of these functions are called
until after the loop is over, so the original bug would be back.

Perhaps 'once' is too misleading a name, given the confusion you
alluded to earlier. Maybe we could use 'capture' instead? A capture
expression would be captured at every function definition time,
period. Capture expressions outside functions would be illegal or
limited to compile-time constant expressions (unless someone has a
better idea). A capture expression inside "if 0:" would still be
captured to simplify the semantics (unless the compiler can prove that
it has absolutely no side effects).

--Guido van Rossum (home page: http://www.python.org/~guido/)

More information about the Python-Dev mailing list