[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