[Python-ideas] Explicit variable capture list

Steven D'Aprano steve at pearwood.info
Wed Jan 20 19:52:25 EST 2016


On Wed, Jan 20, 2016 at 01:58:46PM -0500, Terry Reedy wrote:
> On 1/20/2016 11:48 AM, Guido van Rossum wrote:
> >But 'shared' and 'local' are both the wrong words to use here. Also
> >probably this should syntactically be tied to the function header so the
> >time of evaluation is clear(er).
> 
> Use ';' in the parameter list, followed by name=expr pairs.  The 
> question is whether names after are initialized local variables, subject 
> to rebinding at runtime, or named constants, with the names replaced by 
> the values at definition time.  In the former case, a type hint could by 
> included.  In the latter case, which is much better for optimization, 
> the fixed object would already be typed.
> 
> def f(int a, int b=1; int c=2) => int

I almost like that.

The problem is that the difference between ; and , is visually 
indistinct and easy to mess up. I've occasionally typed ; in a parameter 
list and got a nice SyntaxError telling me I've messed up, but with your 
suggestion the function will just silently do the wrong thing.

I suggest a second "parameter list":

def func(a:int, b:int=1)(c:int)->int:
    ...


is morally equivalent to:

def func(a:int, b:int=1, c:int=c)->int:
    ...


except that c is not a parameter of the function and cannot be passed 
as an argument:

func(a=0, b=2)  # okay
func(a=0, b=2, c=1)  # TypeError


We still lack a good term for what the (c) thingy should be called. I'm 
not really happy with either of "static" or "capture", but for lack of 
anything better I'll go with capture for the moment.

So a full function declaration looks like:

    def NAME ( PARAMETERS ) ( CAPTURES ) -> RETURN-HINT :

(Bike-shedders: do you prefer () [] or {} for the list of captures?)

CAPTURES is a comma-delimitered list of local variable names, with 
optional type hint and optional bindings. Here are some examples:

    # Capture the values of x and y from the enclosing scope.
    # Both x and y must exist at func definition time.
    def func(arg)(x, y):
        # inside the body of func, x and y are locals


    # Same as above, except with type-hinting.
    # If x or y in the enclosing scope are not floats,
    # a checker should report a type error.
    def func(arg)(x:float, y:float):
        # inside the body of func, x and y are locals


    # Capture the values of x and y from the enclosing scope,
    # binding to names x and z.
    # Both x and y must exist at func definition time.
    def func(arg)(x, z=y):
        # inside the body of func, x and z are locals
        # while y would follow the usual scoping rules


    # Capture a copy of the value of dict d from the enclosing scope.
    # d must exist at func definition time.
    def func(arg)(d:dict=d.copy()):
        # inside the body of func, d is a local


If a capture consists of a name alone (or a name plus annotation), it 
declares a local variable of that name, and binds to it the captured 
value of the same name in the enclosing scope. E.g.:

x = 999
def func()(x):  # like x=x
    x += 1
    return (x, globals()['x'])

assert func() == (1000, 999)
x = 0
assert func() == (1000, 0)


If a capture consists of a name = expression, the expression is 
evaluated at function definition time, and the result captured.

y = 999
def func()(x=y+1):
    return x

assert func() == 1000
del y
assert func() == 1000



Can we make this work with lambda? I think we can. The current lambda 
syntax is:

lambda params: expression

e.g. lambda x, y=y: x+y


Could we keep that (for backwards compatibility) but allow parens to 
optionally surround the parameter list? If so, then we can allow an 
optional second set of parens after the first, allowing captures:

lambda (x)(y): x+y


The difference between 

    lambda x,y=y: ... 

and 

    lambda (x)(y): ... 

is that the first takes two arguments, mandatory x and optional y (which 
defaults to the value of y from the enclosing scope), while the second 
only takes one argument, x.


-- 
Steve


More information about the Python-ideas mailing list