[Python-ideas] Statement local functions and classes (aka PEP 3150 is dead, say 'Hi!' to PEP 403)

Nick Coghlan ncoghlan at gmail.com
Thu Oct 13 07:48:03 CEST 2011


On Thu, Oct 13, 2011 at 2:45 PM, Carl M. Johnson
<cmjohnson.mailinglist at gmail.com> wrote:
> I really like the proposal, although I would be interested to see if anyone can bikeshed up keywords that might be better than : or @… :-)
>
> I do have some questions about it. Does using @ instead of the name defined below make the implementation easier? In other words, would this cause a NameError on normalize because normalize isn't defined in the local namespace?--
>
> :sorted_list = sorted(original, key=normalize)
> def normalize(item):
>   …
>
> Or is the @ just for brevity? I assume the point is that it's not just brevity, but you have to use the @ in order to make the implementation straightforward.

The brevity and highlighting the "forward reference" are actually the
primary motivation, making the implementation easier (which it does
do) is a nice bonus.

When I first started writing up the PEP, I was using a syntax more
directly inspired by PEP 3150's given clause:

    :sorted_list = sorted(original, key=@) given (item):
        …

There were a few problems with this:
1. It pushes the signature of the callable all the way over to the RHS
2. The callable is actually anonymous, so @.__name__ would always be
"<given>". That's annoying for the same reason as it's annoying in
lambda expressions.
3. It doesn't provide any real clues that the body of the statement is
actually a nested function

So then I thought, "well what if I use 'def' instead of 'given' as the
keyword"? At that point, it was only a short leap to realising that
what I wanted was really close to "arbitrary simple statement as a
decorator". So I rewrote things to the format in the posted PEP:

    :sorted_list = sorted(original, key=@)
    def normalise(item):
        …

Now that the nested function is being given a name, I realised I
*could* just refer to it by that name in the block prefix. However, I
left it alone for the reasons I mention above:

1. It highlights that this is not a normal name reference but
something special (i.e. a forward reference to the object defined by
the statement)
2. The fact that references are always a single character makes it
easier to justify giving the function itself a nice name, which
improves introspection support and error messages. In genuine
throwaway cases, you can always use a dummy name like 'f', 'func',
'g', 'gen' or 'block' or 'attr' depending on what you're doing.
3. Multiple references don't make the block prefix unwieldy (although
the use cases for those are limited)
4. It does make the implementation easier, since you don't have to
worry about namespace clashes - the function remains effectively
anonymous in the containing scope.

It would be possible to extend the PEP to include the idea of allowing
the name to be omitted in function and class definitions, but that's
an awful lot of complexity when it's easy to use a throwaway name if
you genuinely don't care.

Just like PEP 3150, all of this is based on the premise that one of
the key benefits of multi-line lambdas is that it lets you do things
in the *right order* - operation first, callable second. Ordinary
named functions force you to do things the other way around. Decorator
abuse is also a problematic approach, since even though it gets the
order right, you end up with something that looks like an ordinary
function or class definition but is actually nothing of the sort.

Oh, I'll also note that the class variant gives you the full power of
PEP 3150 without any (especially) funky new namespace semantics:

    :x = property(@.get, @.set, @.delete)
    class scope:
        def get(self):
            return __class__.attr
        def set(self, val):
            __class__.attr = val
        def delete(self):
            del __class__.attr

Cheers,
Nick.

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



More information about the Python-ideas mailing list