[Python-ideas] Allowing def to assign to anything

Andrew Barnert abarnert at yahoo.com
Mon Oct 26 15:30:05 EDT 2015


On Oct 26, 2015, at 06:45, Steven D'Aprano <steve at pearwood.info> wrote:
> 
>> On Mon, Oct 26, 2015 at 02:02:00AM -0400, Alexander Walters wrote:
>> 
>> In my code, I write a lot of dispatch dictionaries (for lack of a switch 
>> statement, but I will not hold my breath for that).  In trying to make 
>> writing these dictionaries less annoying, I tend to use many lambdas.  I 
>> can let you guess at what problems that has resulted in.  Of course, the 
>> preferred way to write such dictionaries is by using a regular function, 
>> and adding that function to a dictionary.  This isn't exactly a problem 
>> - it works, and works well, but it is annoying to write, and leaves 
>> artifacts of those functions in module scope.  I propose a little bit of 
>> sugar to make this a little less annoying.
> 
> In this case, leaving "artifacts" in the module scope is a feature. If 
> your function is simple enough to express in a simple expression, then a 
> lambda may be the right solution. But if it requires a full block, then 
> chances are that it's too complex for it to be obviously correct, which 
> means you should test it.

There is a bit of a boundary here: some functions are a single statement, which can't be written as a lambda, but are just as trivial as functions that can be. And functions that require two statements aren't that much more complex.

And meanwhile, there are plenty of people who twist things into knots to fit things in expressions that don't belong as expressions just so they can use lambda, and this proposal could give their colleagues/teacher/conscience a better way to say "that should be a def" and answer the objections about "but that would mean 3 lines of boilerplate for a 2-line function".

Of course without a realistic example instead of just the empty toy definitions in the original proposal, it's hard to see if there really would be such benefits here, or if it's just a theoretical possibility that would almost never arise, so it's definitely worth you pointing out this issue.

> Giving the function a name and module scope 
> supports testing.

It's not that hard to write unit tests that call dispatcher['spam'] instead of calling spam. Your unit test framework may not do this out of the box, but if you're going to be testing lots of functions like this, that's just something you have to add support for once.

> But if you really want to get rid of it:
> 
> del the_function
> 
> after adding it to the dispatch table.

Or, if you're worried about people calling them accidentally, just have the decorator return None so that's an error. Or, if you're worried about them showing up in tab completion, import *, etc., prefix them with _ and/or leave them out of __all__. The only time "polluting the namespace" is literally a concern is if you might have something else with the same name and you don't want one of them to erase the other; usually it's one of these other things you're really concerned with.

> Or, stick them in their own namespace. For a package, that might mean 
> moving the dispatch table and its associated functions into its own 
> module. Or, put them in a class:

Or just local to a function:

    def make_dispatch_table():
        def spam(x): pass
        def eggs(x): pass
        def cheese(x): pass
        return {'spam': spam, 'eggs': eggs, 'cheese': cheese}

Or, if you really want, you could even replace the last line with:

        return {k: v for (k, v) in locals().items() if callable(v)}

Anyway, it's worth noting that your submodule idea and the local function idea share something in common: because there is a scope the functions are defined in, and they still live in that scope, they can call each other. The OP's suggestion, and your del suggestion, make that impossible. Also, inspect, or manual inspection by hacking away at the REPL, will work a lot more easily.

I still think this proposal (suitably worked out) isn't a bad idea, but I agree that you've given good reasons why it isn't necessary for the stated use case, and why we need better and/or more complete examples to evaluate it.

>> If `def` is allowed to assign to anything (anything that is legal at the 
>> left hand side of an = in that scope), annoying artifacts go away.  The 
>> syntax I propose should be backwards compatible.
> 
> Assign to *anything*? 

This is part of why I was trying to get him to refine it into a complete specification instead of a vague idea.

The start of the answer here is pretty obvious: "def" takes an assignment target—not a target list, or another assignment, even though of course both of those are legal on the left side of an "=". (And further extensions to iterable unpacking, or even full pattern-matching assignment, wouldn't change what "target" means.)

But that still isn't a complete answer. There are things that are allowed as targets by the grammar, but are still syntax errors ("*spam = 2") in an assignment, and I'm pretty sure the list of such things would be slightly different for def statements. (For example, a target can be a parenthesized target list, or a slicing. For a single assignment, that's not a syntax error, although of course it's a type error at runtime if the value isn't iterable; for a function definition, that should probably be a syntax error.) So someone still needs to write out a semantic specification like the one in the docs for assignment statements.

But at least starting with "an assignment target" instead of "anything to the left of '='" is a start. It's as good as writing out the grammar, and a lot easier for most people to understand, and I think it gets the intuitive idea across. 


More information about the Python-ideas mailing list