[Python-ideas] Explicit variable capture list

Guido van Rossum guido at python.org
Mon Jan 25 13:52:26 EST 2016


On Sun, Jan 24, 2016 at 10:32 PM, Terry Reedy <tjreedy at udel.edu> wrote:
> What I've concluded from this thread is that function definitions (with
> direct use 'def' or 'lambda') do not fit well within loops, though I used
> them there myself.

Right. When you can avoid them, you avoid extra work in an inner loop,
which is often a good idea.

> When delayed function calls are are needed, what belongs within loops is
> packaging of a pre-defined function with one or more arguments within a
> callable.  Instance.method is an elegant syntax for doing so.
> functools.partial(func, args, ...) is a much clumsier generalized
> expression, which requires an import.  Note that 'partial' returns a
> function for delayed execution even when a complete, not partial, set of
> arguments is passed.

Right. I've always hated partial() (which is why it's not a builtin)
because usually a lambda is clearer (it's difficult to calculate in
your head the signature of the thing it returns from the  arguments
passed), but this is one thing where partial() wins, since it captures
values.

> A major attempted (and tempting) use for definitions within a loop is
> multiple callbacks for multiple gui widgets, where delayed execution is
> needed.  The three answers to multiple 'why doesn't this work' on both
> python-list and Stackoverflow are multiple definitions with variant 'default
> args', a custom make_function function outside the loop called multiple
> times within the loop, and a direct function outside the loop called with
> partial within the loop.  I am going to start using partial more.

Yes, the make_function() approach is just a custom partial().

> Making partial a builtin would make it easier to use and more attractive.
> Even more attractive would be syntax that abbreviates delayed calls with
> pre-bound arguments in the way that inst.meth abbreviates a much more
> complicated expression roughly equivalent to "bind(inst.__getattr__('meth'),
> inst)".

A recommended best practice / idiom is more useful, because it can be
applied to all Python versions.

> A possibility would be to make {} a delayed and possibly partial call
> operator, in parallel to the current use of () as a immediate and total call
> operator.
>   expr{arguments}
> would evaluate to a function, whether of type <function> or a special class
> similar to bound methods. The 'arguments' would be anything allowed within
> partial, which I believe is anything allowed in any function call.  I chose
> {} because expr{...} is currently illegal, just as expr(arguments) is for
> anything other than a function call.  On the other hand, expr[...] is
> currently legal, at least up to '[', as is expr<...> at least up to '<'.

-1 on expr{...}.

>>> and pass the url as a parameter to submit(), problem solved, and you
>>> waste fewer resources on function objects and cells to hold nonlocals.
>
> executor.submit appears to me to be a specialized version of partial, with
> all arguments required.  With the proposal above, I think submit(func{all
> args}) would work.

But not before 3.6.

>> Aye, that's how the current example code in the docs handles it -
>> there's an up front definition of the page loading function, and then
>> the submission to the executor is with a dict comprehension.
>
> I presume you both are referring to ThreadPoolExecutor Example.  The
> load_url function, which I think should be 'get_page' has a comment that is
> wrong (it does not 'report the url') and no docstring.  My suggestion:
>
> # Define an example function for the executor.submit call below.
> def get_page(url, timeout):
>     "Return the page, as a string, retrieved from the url."
>     with ...
>
>> The only thing "wrong" with it is that when reading the code, the
>> potentially single-use function is introduced first without any
>> context, and it's only later that you get to see what it's for.
>
> A proper comment would fix this I think.  That aside, if the main code were
> packaged within def main, as in the following ProcessPoolExecutor Example,
> so as to delay the lookup of 'load_url' or 'get_page', then the two
> functions definitions could be in *either* order.  The general convention in
> Pythonland seems to be to put main last (bottom up, define everything before
> use), but in a recent python-list thread, at least one person, and I think
> two, said they like to start with def main (top down style, which you seem
> to like).

I like both. :-)

-- 
--Guido van Rossum (python.org/~guido)


More information about the Python-ideas mailing list