[Python-ideas] Tweaking closures and lexical scoping to include the function being defined

Nick Coghlan ncoghlan at gmail.com
Wed Sep 28 12:45:08 CEST 2011

On Wed, Sep 28, 2011 at 6:26 AM, Paul Moore <p.f.moore at gmail.com> wrote:
> On 28 September 2011 08:10, Paul Moore <p.f.moore at gmail.com> wrote:
>> That either validates the default-argument hack as a valid response to
>> a specific requirement, or suggests some syntax added to the function
>> definition line. Or of course, just go with a normal (nested function)
>> closure.
> Taking a step back from all this, using an explicit closure to
> implement the counter example being used throughout this thread looks
> something like this:
>>>> # I thought apply still existed in some module. Never mind, can't find it so I'll reimplement a quick version here...
>>>> def apply(f): return f()
> ...
>>>> @apply
> ... def counter():
> ...     n = 1
> ...     def inner():
> ...         nonlocal n
> ...         print(n)
> ...         n += 1
> ...     return inner
> ...
>>>> counter()
> 1
>>>> counter()
> 2
>>>> counter()
> 3
> To be honest, that doesn't actually look that bad. Explicit is better
> than implicit and all that, and the boilerplate doesn't really bother
> me that much.
> The real annoying boilerplate here is the "def dummy_name()...return
> dummy_name" bit. But fixing that leads us directly back to the
> perennial discussion on anonymous multiline functions...

Actually, there are some additional aspects that annoy me:
- the repetition of the variable name 'n'
- the scope of the variable name is wrong (since we only need it in
the inner function)
- indentation matters (cf try/except/finally)
- hidden signature for the actual function
- hoops to jump through to get decent introspection values (e.g. __name__)

I find the following 5 line toy example:

from threading import Lock

def global_counter() [n=1, lock=Lock()]:
    with lock:
        n += 1

Far more readable than the 10-line closure equivalent:

from threading import Lock

@apply # I sometimes wonder if we should bring this back in functools...
def global_counter():
    n = 1
    local = Lock()
    def global_counter():
        with lock:
            n += 1
    return global_counter

Or the 10-line 7-self class equivalent:

from threading import Lock

class global_counter:
    def __init__(self):
        self.n = 1
        self.lock = Lock()

    def __call__(self):
        with self.lock:
            self.n += 1


Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia

More information about the Python-ideas mailing list