Allowing def to assign to anything

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. 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. ``` dispatch = {} def dispatch['foo'](bar): return bar * bar ``` Does this make anything possible that is impossible now? No. But it does make the intent of the module author clear - the function is only ever intended to live inside that dict, or list, or other structure. This, to me, is less annoying to write, and is more readable. This obviously could be used outside of creating dispatch dictionaries, but that is the use case I would benefit from.

Decorators easily support this: dispatch_table = {} def dispatch(arg): def inner(func): dispatch_table[arg] = func return func return inner @dispatch('foo') def dispatch_foo(bar): pass --- Bruce Check out my new puzzle book: http://J.mp/ingToConclusions Get it free here: http://J.mp/ingToConclusionsFree (available on iOS) On Sun, Oct 25, 2015 at 11:02 PM, Alexander Walters <tritium-list@sdamon.com
wrote:

This too leaves artifacts in the module, which is one of the annoyances I am trying to eliminate. Though I do admit that it makes the intent of the author clear. It still feels less pythonic to me than allowing a statement that assigns to just assign to anything assignable. On 10/26/2015 02:15, Bruce Leban wrote:

On Sun, Oct 25, 2015 at 11:19 PM, Alexander Walters <tritium-list@sdamon.com
wrote:
Well, to some extent features that are already in the language feel more pythonic than features that aren't. Given that this is already easy to do, and the decorator solution is more powerful, the bar for modifying the language is higher. That aside, I like the fact that I can find these functions in the module. For one thing, it makes them testable in an obvious way (without going through the dispatch dictionary). Generally, module_name.function_name doesn't change during the course of program execution but there's no such expectation for dispatch_dict[function_name]. Furthermore, consider that def dispatch['foo'](bar): pass might look like a simple dict reference but is actually a call to __setitem__ which means this can have arbitrary side effects. Yes @decorators can have arbitrary side effects too. How do I find out the respective side effects? Perhaps: help(decorator) # obvious help(dispatch) # nope help(dispatch.__setitem__) # not obvious to me --- Bruce Check out my new puzzle book: http://J.mp/ingToConclusions Get it free here: http://J.mp/ingToConclusionsFree (available on iOS)

On 2015-10-26 07:27, Ian Kelly wrote:
Would they be simpler to parse if they were: def (bar) as dispatch['foo']: return bar * bar and: def (x) as foo(bar)[baz]: return x ? In these instances, if there's no name then it should have the "as ..." part; if there _is_ a name, then it shouldn't have the "as ..." part. It's possible that: def foo(bar): return x could just be a shorter way of writing: def (bar) as foo: return x

On Oct 25, 2015, at 23:02, Alexander Walters <tritium-list@sdamon.com> 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.
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.
Seems interesting. What's the name of the defined function? For an attribution like "spam.eggs" you'd probably want it to be "eggs", and I guess for "spam['eggs']" as well, but what about "spam['two words']" or "spam[2]"? I assume the qualname is just the name. Also, would this go through the descriptor mechanism if you def an attribution?

I imagine in the case of assigning to a class or instance, the name would be the same (and binding to self would act the same) as if it were assigned in the traditional way. I do not propose that assigning to classes in this way be considered a good idea. as for when assigned to a data structure, my admittedly naive idea would be to set the name to '__none__' or some such. I am open to other ideas though. On 10/26/2015 02:19, Andrew Barnert wrote:

On Oct 25, 2015, at 23:23, Alexander Walters <tritium-list@sdamon.com> wrote:
I imagine in the case of assigning to a class or instance, the name would be the same (and binding to self would act the same) as if it were assigned in the traditional way.
In what traditional way? When you def a function, its name is the name given in the def statement. If you later assign it to a member of an object, that doesn't change its name. So, that doesn't answer the question. As for the binding to self, that doesn't happen at assignment time, so it can't happen the same way as at assignment time. The binding happens later, each time the method is looked up. If an object doesn't have an attribute of the looked up name, but its type does, the type's attribute's __get__ method is called with the instance. So, there's nothing to do here at all; functions already have a __get__ method that returns a bound method (and if you assign a function to an instance rather than a type, it doesn't get bound). This is all explained pretty clearly in the descriptor HOWTO. But descriptors also work for setting, not just getting (which is how @property works), and that's what I was asking about. If I write "spam.eggs = 0", and type(spam) has a member named "eggs", its __set__ method will get called with the instance (spam) and the value (0). So, if I write "def spam.eggs(): pass", does it call the same descriptor method?
I do not propose that assigning to classes in this way be considered a good idea.
OK, but unless you're actually proposing to not allow it, you still need to work out what it would do.
as for when assigned to a data structure, my admittedly naive idea would be to set the name to '__none__' or some such. I am open to other ideas though.
Functions defined with lambda get the name "<lambda>", so you'd probably want angle brackets here as well. But otherwise that seems reasonable, I guess.

to be clear, I propose the following to be equivalent (to clear up what I mean) ``` # classes: class Foo: def bar(self): pass # vs class Foo: pass def Foo.bar(self): pass # Instances: def foo(args): pass inst = Class() inst.foo = foo # vs inst = Class() def inst.foo(args): pass ``` does this make that clear? On 10/26/2015 03:05, Andrew Barnert wrote:

On Oct 26, 2015, at 00:09, Alexander Walters <tritium-list@sdamon.com> wrote:
to be clear, I propose the following to be equivalent (to clear up what I mean)
Yes, that clears it up. This is what I thought you probably wanted. But I think it would be better to keep it simpler. Instead of this:
… your first example would be simpler if it were equivalent to this: class Foo: pass def _dummy(self): pass Foo.bar = _dummy (except of course not binding anything to "_dummy"). Notice that this is now exactly like your second example, with an instance, so we only need one rule instead of two. And in simple cases it does exactly what you want here, too. And in complex cases, e.g., involving "__class__" or no-argument "super" or names accessible in the class-definition scope, the implementation doesn't need to do any magic, while with your version, it would. (That magic might be convenient in some cases, but since you don't want to encourage this kind of use anyway, why make things more complicated just to make the discouraged use more convenient?) In fact, I would make this as simple as possible: Just change the "funcname" in the syntax from an identifier to a target. Then, any "def" statement whose works by defining a function, then assigning it to the target (which is legal iff the target is a legal assignment target). It's a perfectly normal assignment, following all the same __setitem__, __setattr__, descriptor, etc. rules as any other assignment. That does mean the name of the function ends up being "Foo.bar" or "foo.bar" or "foo['bar']", and at least for the first two could end up being confusing, and even more so for the qualname, so maybe you'd want to add one minor tweak: if the target is not an identifier, the name is just "<none>" or "<anon>" or something. But I don't think you want to go any farther. (In particular, making any of the examples come out as "bar" is just extra complexity that only improves uses you don't want to encourage.)

On 10/26/2015 03:55, Andrew Barnert wrote:
Agreed, that is a better idea.
In fact, I would make this as simple as possible: Just change the "funcname" in the syntax from an identifier to a target. Then, any "def" statement whose works by defining a function, then assigning it to the target (which is legal iff the target is a legal assignment target). It's a perfectly normal assignment, following all the same __setitem__, __setattr__, descriptor, etc. rules as any other assignment.
That does mean the name of the function ends up being "Foo.bar" or "foo.bar" or "foo['bar']", and at least for the first two could end up being confusing, and even more so for the qualname, so maybe you'd want to add one minor tweak: if the target is not an identifier, the name is just "<none>" or "<anon>" or something. But I don't think you want to go any farther. (In particular, making any of the examples come out as "bar" is just extra complexity that only improves uses you don't want to encourage.)
The more I think about it function.__name__ when assigned like this might be better served as `'<anon: %s>' % (whatever_you_assigned_to,)`

On Oct 26, 2015, at 01:01, Alexander Walters <tritium-list@sdamon.com> wrote:
Actually, now that I think about it, I think that would be up to each implementation—I don't think Python specifies "<lambda>" as the name for lambdas, that's just what CPython does, so the same would be true here, right? But yeah, now that I see the idea, I definitely like '<anon: foo.bar>' a lot better than '<anon>' or 'foo.bar'.

On Mon, Oct 26, 2015 at 6:05 PM, Andrew Barnert via Python-ideas <python-ideas@python.org> wrote:
dispatch = {} def dispatch['foo'](): pass assert dispatch['foo'].__name__ == "dispatch['foo']" I have no problem with this, conceptually. Syntactically, it would be defined as f"dispatch[{key!r}]" if the key is allowed to be an arbitrary expression. ChrisA

On Oct 26, 2015, at 19:48, Chris Angelico <rosuav@gmail.com> wrote:
The paragraph you're replying to was about the naming for "def foo.bar():", which Alexander had suggested would work differently from "def foo['bar']():" (which he later retracted). So I'm not sure why you're proposing a different alternative for "def foo['bar']():" in response to these paragraphs. At any rate, later in the thread, Alexander suggested something like "<anon: dispatch['foo']>", which I like better. The angle brackets and "anon" tell you that it's not guaranteed to be a REPL-friendly name—which would be possible in this case, but not in general. Plus, the part after the "anon" is just the text of the target node, so it's dead simple. (And if you're worried that this means the function's repr ends up with nested angle brackets—well, it does, but the same is true for lambda functions, and the hidden functions on comprehensions.)
But surely dispatch is also allowed to be an arbitrary expression? And, similarly, foo in foo.bar? And in those cases, the repr of those parts is worse than useless (you'd get the contents of dispatch instead of its name), so there's really no alternative. Also, your suggestion means that this will assert: dispatch = {} def dispatch["foo"](): pass assert dispatch['foo'].__name__ == 'dispatch["foo"]' … will assert, so it's a pretty weak DWIM anyway. Alexander's solution doesn't have any of those problems.

On Tue, Oct 27, 2015 at 2:48 PM, Andrew Barnert <abarnert@yahoo.com> wrote:
There've been some weirdnesses in how emails get through, and I'm not sure which previous emails I'd actually received at the time when I wrote that. Hopefully it's just a temporary outage that's now cured. If my post makes no sense in the full context of the thread, feel free to ignore it. ChrisA

On Mon, Oct 26, 2015 at 5:02 PM, Alexander Walters <tritium-list@sdamon.com> wrote:
I agree; the idea has been raised a few times, and I think it'd be helpful. It's probably not necessary to allow the _entire_ scope of "anything legal on the left of =", as that's pretty broad; even if the only form allowed were obj[key], it'd be useful. But for building a dispatch dictionary, you could simply decorate your functions with a capturer: dispatch = {} def cmd(func): dispatch[func.__name__] = func return func @cmd def foo(bar): return bar * bar You can even merge the decorator and the dict itself: class DispatchDict(dict): def __call__(self, func): self[func.__name__] = func return func dispatch = DispatchDict() @dispatch def foo(bar): return bar * bar This does require that your dict keys be legal identifiers (you can't do "def dispatch['!'](x):" as "@cmd def !(x)"), but for a lot of common cases, this does work. I've used this style for building argparse UIs and such, and it's a lot easier than most other options I've played with. ChrisA

On 10/26/2015 02:20, Chris Angelico wrote:
I agree, that perhaps the scope could be a little wide when put this way, but my instinct is that 'allow anything already legal for =' would be the path of least frustration when implementing this. I could be woefully wrong. I do at least wish to assign to object with __setitem__ defined.
This does, indeed, make life a bit easier in the here and now (and is similar to kitbashed techniques I already use). I am hoping to make that obsolete.
- Alex

On Mon, Oct 26, 2015 at 5:30 PM, Alexander Walters <tritium-list@sdamon.com> wrote:
If you start by allowing only obj[key] (aka obj.__setitem__), and maybe with only string literals or simple names for the key, it would be a strict subset of Python syntax. That means that a future proposal can easily expand on that (*un*like, for instance, the way "{obj[key]}".format(obj=obj) operates), while providing only the one or two most common usages: def dispatch["foo"](x, y): pass name = "bar" def dispatch[name](x, y): pass
Sure. You'll need some strong use-cases that justify _not_ using this technique, though; since there's an existing way to do this, the bar for new syntax is "show why the current way isn't good enough". If you're worried about junk in your globals, what you could do is: dispatch = {} def cleanup(): self = cleanup # we're going to delete ourselves, so snapshot to local for n,v in globals(): if v is self: del globals()[n] def cmd(func): dispatch[func.__name__] = func return cleanup @cmd def foo(): pass @cmd def bar(): pass cleanup() # This function will self-destruct in three seconds. Two... one... After all, a decorator can return anything it likes! ChrisA

On Mon, Oct 26, 2015 at 02:02:00AM -0400, Alexander Walters wrote:
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. Giving the function a name and module scope supports testing. But if you really want to get rid of it: del the_function after adding it to the dispatch table. 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: class Switch: def cheese(x): ... def spam(x): ... def eggs(x): ... dispatch = {} dispatch.update(Switch.__dict__) result = dispatch[key](arg) Or one could write a class decorator (or a metaclass) to post-process the class and returns whatever you like. If I were doing this a lot, I would invest the time in building a nice switch construct, before looking for new syntax.
Assign to *anything*? a, b, c = 1, 2, 3 def a, b, c(x, y): ... I don't see that, at least, working.
It's not clear to me. To me, it looks like you've badly mistyped a function annotation. If you hadn't explained what you wanted, I wouldn't have a clue what "def dispatch['foo'](bar)" meant. -- Steve

On Oct 26, 2015, at 06:45, Steven D'Aprano <steve@pearwood.info> wrote:
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.
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 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.
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.

On 10/26/2015 9:45 AM, Steven D'Aprano wrote:
dispatch = dict(Switch.__dict__) Except for the extraneous dunder names in __dict__, I like this. For the fastidious, dispatch = {k:v for k, v in Switch.__dict__.items() if k[0] != '_'} This could be wrapped in a function that would take either a module (globals()) or class (.__dict__) as the namespace source. -- Terry Jan Reedy

You can just do: dispatch = {} def dispatcher(f): dispatch[f.__name__] = f @dispatcher def foo(bar): return bar * bar I really think that might be weird and kind of jarring to read... On October 26, 2015 1:02:00 AM CDT, Alexander Walters <tritium-list@sdamon.com> wrote:
-- Sent from my Nexus 5 with K-9 Mail. Please excuse my brevity.

Decorators easily support this: dispatch_table = {} def dispatch(arg): def inner(func): dispatch_table[arg] = func return func return inner @dispatch('foo') def dispatch_foo(bar): pass --- Bruce Check out my new puzzle book: http://J.mp/ingToConclusions Get it free here: http://J.mp/ingToConclusionsFree (available on iOS) On Sun, Oct 25, 2015 at 11:02 PM, Alexander Walters <tritium-list@sdamon.com
wrote:

This too leaves artifacts in the module, which is one of the annoyances I am trying to eliminate. Though I do admit that it makes the intent of the author clear. It still feels less pythonic to me than allowing a statement that assigns to just assign to anything assignable. On 10/26/2015 02:15, Bruce Leban wrote:

On Sun, Oct 25, 2015 at 11:19 PM, Alexander Walters <tritium-list@sdamon.com
wrote:
Well, to some extent features that are already in the language feel more pythonic than features that aren't. Given that this is already easy to do, and the decorator solution is more powerful, the bar for modifying the language is higher. That aside, I like the fact that I can find these functions in the module. For one thing, it makes them testable in an obvious way (without going through the dispatch dictionary). Generally, module_name.function_name doesn't change during the course of program execution but there's no such expectation for dispatch_dict[function_name]. Furthermore, consider that def dispatch['foo'](bar): pass might look like a simple dict reference but is actually a call to __setitem__ which means this can have arbitrary side effects. Yes @decorators can have arbitrary side effects too. How do I find out the respective side effects? Perhaps: help(decorator) # obvious help(dispatch) # nope help(dispatch.__setitem__) # not obvious to me --- Bruce Check out my new puzzle book: http://J.mp/ingToConclusions Get it free here: http://J.mp/ingToConclusionsFree (available on iOS)

On 2015-10-26 07:27, Ian Kelly wrote:
Would they be simpler to parse if they were: def (bar) as dispatch['foo']: return bar * bar and: def (x) as foo(bar)[baz]: return x ? In these instances, if there's no name then it should have the "as ..." part; if there _is_ a name, then it shouldn't have the "as ..." part. It's possible that: def foo(bar): return x could just be a shorter way of writing: def (bar) as foo: return x

On Oct 25, 2015, at 23:02, Alexander Walters <tritium-list@sdamon.com> 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.
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.
Seems interesting. What's the name of the defined function? For an attribution like "spam.eggs" you'd probably want it to be "eggs", and I guess for "spam['eggs']" as well, but what about "spam['two words']" or "spam[2]"? I assume the qualname is just the name. Also, would this go through the descriptor mechanism if you def an attribution?

I imagine in the case of assigning to a class or instance, the name would be the same (and binding to self would act the same) as if it were assigned in the traditional way. I do not propose that assigning to classes in this way be considered a good idea. as for when assigned to a data structure, my admittedly naive idea would be to set the name to '__none__' or some such. I am open to other ideas though. On 10/26/2015 02:19, Andrew Barnert wrote:

On Oct 25, 2015, at 23:23, Alexander Walters <tritium-list@sdamon.com> wrote:
I imagine in the case of assigning to a class or instance, the name would be the same (and binding to self would act the same) as if it were assigned in the traditional way.
In what traditional way? When you def a function, its name is the name given in the def statement. If you later assign it to a member of an object, that doesn't change its name. So, that doesn't answer the question. As for the binding to self, that doesn't happen at assignment time, so it can't happen the same way as at assignment time. The binding happens later, each time the method is looked up. If an object doesn't have an attribute of the looked up name, but its type does, the type's attribute's __get__ method is called with the instance. So, there's nothing to do here at all; functions already have a __get__ method that returns a bound method (and if you assign a function to an instance rather than a type, it doesn't get bound). This is all explained pretty clearly in the descriptor HOWTO. But descriptors also work for setting, not just getting (which is how @property works), and that's what I was asking about. If I write "spam.eggs = 0", and type(spam) has a member named "eggs", its __set__ method will get called with the instance (spam) and the value (0). So, if I write "def spam.eggs(): pass", does it call the same descriptor method?
I do not propose that assigning to classes in this way be considered a good idea.
OK, but unless you're actually proposing to not allow it, you still need to work out what it would do.
as for when assigned to a data structure, my admittedly naive idea would be to set the name to '__none__' or some such. I am open to other ideas though.
Functions defined with lambda get the name "<lambda>", so you'd probably want angle brackets here as well. But otherwise that seems reasonable, I guess.

to be clear, I propose the following to be equivalent (to clear up what I mean) ``` # classes: class Foo: def bar(self): pass # vs class Foo: pass def Foo.bar(self): pass # Instances: def foo(args): pass inst = Class() inst.foo = foo # vs inst = Class() def inst.foo(args): pass ``` does this make that clear? On 10/26/2015 03:05, Andrew Barnert wrote:

On Oct 26, 2015, at 00:09, Alexander Walters <tritium-list@sdamon.com> wrote:
to be clear, I propose the following to be equivalent (to clear up what I mean)
Yes, that clears it up. This is what I thought you probably wanted. But I think it would be better to keep it simpler. Instead of this:
… your first example would be simpler if it were equivalent to this: class Foo: pass def _dummy(self): pass Foo.bar = _dummy (except of course not binding anything to "_dummy"). Notice that this is now exactly like your second example, with an instance, so we only need one rule instead of two. And in simple cases it does exactly what you want here, too. And in complex cases, e.g., involving "__class__" or no-argument "super" or names accessible in the class-definition scope, the implementation doesn't need to do any magic, while with your version, it would. (That magic might be convenient in some cases, but since you don't want to encourage this kind of use anyway, why make things more complicated just to make the discouraged use more convenient?) In fact, I would make this as simple as possible: Just change the "funcname" in the syntax from an identifier to a target. Then, any "def" statement whose works by defining a function, then assigning it to the target (which is legal iff the target is a legal assignment target). It's a perfectly normal assignment, following all the same __setitem__, __setattr__, descriptor, etc. rules as any other assignment. That does mean the name of the function ends up being "Foo.bar" or "foo.bar" or "foo['bar']", and at least for the first two could end up being confusing, and even more so for the qualname, so maybe you'd want to add one minor tweak: if the target is not an identifier, the name is just "<none>" or "<anon>" or something. But I don't think you want to go any farther. (In particular, making any of the examples come out as "bar" is just extra complexity that only improves uses you don't want to encourage.)

On 10/26/2015 03:55, Andrew Barnert wrote:
Agreed, that is a better idea.
In fact, I would make this as simple as possible: Just change the "funcname" in the syntax from an identifier to a target. Then, any "def" statement whose works by defining a function, then assigning it to the target (which is legal iff the target is a legal assignment target). It's a perfectly normal assignment, following all the same __setitem__, __setattr__, descriptor, etc. rules as any other assignment.
That does mean the name of the function ends up being "Foo.bar" or "foo.bar" or "foo['bar']", and at least for the first two could end up being confusing, and even more so for the qualname, so maybe you'd want to add one minor tweak: if the target is not an identifier, the name is just "<none>" or "<anon>" or something. But I don't think you want to go any farther. (In particular, making any of the examples come out as "bar" is just extra complexity that only improves uses you don't want to encourage.)
The more I think about it function.__name__ when assigned like this might be better served as `'<anon: %s>' % (whatever_you_assigned_to,)`

On Oct 26, 2015, at 01:01, Alexander Walters <tritium-list@sdamon.com> wrote:
Actually, now that I think about it, I think that would be up to each implementation—I don't think Python specifies "<lambda>" as the name for lambdas, that's just what CPython does, so the same would be true here, right? But yeah, now that I see the idea, I definitely like '<anon: foo.bar>' a lot better than '<anon>' or 'foo.bar'.

On Mon, Oct 26, 2015 at 6:05 PM, Andrew Barnert via Python-ideas <python-ideas@python.org> wrote:
dispatch = {} def dispatch['foo'](): pass assert dispatch['foo'].__name__ == "dispatch['foo']" I have no problem with this, conceptually. Syntactically, it would be defined as f"dispatch[{key!r}]" if the key is allowed to be an arbitrary expression. ChrisA

On Oct 26, 2015, at 19:48, Chris Angelico <rosuav@gmail.com> wrote:
The paragraph you're replying to was about the naming for "def foo.bar():", which Alexander had suggested would work differently from "def foo['bar']():" (which he later retracted). So I'm not sure why you're proposing a different alternative for "def foo['bar']():" in response to these paragraphs. At any rate, later in the thread, Alexander suggested something like "<anon: dispatch['foo']>", which I like better. The angle brackets and "anon" tell you that it's not guaranteed to be a REPL-friendly name—which would be possible in this case, but not in general. Plus, the part after the "anon" is just the text of the target node, so it's dead simple. (And if you're worried that this means the function's repr ends up with nested angle brackets—well, it does, but the same is true for lambda functions, and the hidden functions on comprehensions.)
But surely dispatch is also allowed to be an arbitrary expression? And, similarly, foo in foo.bar? And in those cases, the repr of those parts is worse than useless (you'd get the contents of dispatch instead of its name), so there's really no alternative. Also, your suggestion means that this will assert: dispatch = {} def dispatch["foo"](): pass assert dispatch['foo'].__name__ == 'dispatch["foo"]' … will assert, so it's a pretty weak DWIM anyway. Alexander's solution doesn't have any of those problems.

On Tue, Oct 27, 2015 at 2:48 PM, Andrew Barnert <abarnert@yahoo.com> wrote:
There've been some weirdnesses in how emails get through, and I'm not sure which previous emails I'd actually received at the time when I wrote that. Hopefully it's just a temporary outage that's now cured. If my post makes no sense in the full context of the thread, feel free to ignore it. ChrisA

On Mon, Oct 26, 2015 at 5:02 PM, Alexander Walters <tritium-list@sdamon.com> wrote:
I agree; the idea has been raised a few times, and I think it'd be helpful. It's probably not necessary to allow the _entire_ scope of "anything legal on the left of =", as that's pretty broad; even if the only form allowed were obj[key], it'd be useful. But for building a dispatch dictionary, you could simply decorate your functions with a capturer: dispatch = {} def cmd(func): dispatch[func.__name__] = func return func @cmd def foo(bar): return bar * bar You can even merge the decorator and the dict itself: class DispatchDict(dict): def __call__(self, func): self[func.__name__] = func return func dispatch = DispatchDict() @dispatch def foo(bar): return bar * bar This does require that your dict keys be legal identifiers (you can't do "def dispatch['!'](x):" as "@cmd def !(x)"), but for a lot of common cases, this does work. I've used this style for building argparse UIs and such, and it's a lot easier than most other options I've played with. ChrisA

On 10/26/2015 02:20, Chris Angelico wrote:
I agree, that perhaps the scope could be a little wide when put this way, but my instinct is that 'allow anything already legal for =' would be the path of least frustration when implementing this. I could be woefully wrong. I do at least wish to assign to object with __setitem__ defined.
This does, indeed, make life a bit easier in the here and now (and is similar to kitbashed techniques I already use). I am hoping to make that obsolete.
- Alex

On Mon, Oct 26, 2015 at 5:30 PM, Alexander Walters <tritium-list@sdamon.com> wrote:
If you start by allowing only obj[key] (aka obj.__setitem__), and maybe with only string literals or simple names for the key, it would be a strict subset of Python syntax. That means that a future proposal can easily expand on that (*un*like, for instance, the way "{obj[key]}".format(obj=obj) operates), while providing only the one or two most common usages: def dispatch["foo"](x, y): pass name = "bar" def dispatch[name](x, y): pass
Sure. You'll need some strong use-cases that justify _not_ using this technique, though; since there's an existing way to do this, the bar for new syntax is "show why the current way isn't good enough". If you're worried about junk in your globals, what you could do is: dispatch = {} def cleanup(): self = cleanup # we're going to delete ourselves, so snapshot to local for n,v in globals(): if v is self: del globals()[n] def cmd(func): dispatch[func.__name__] = func return cleanup @cmd def foo(): pass @cmd def bar(): pass cleanup() # This function will self-destruct in three seconds. Two... one... After all, a decorator can return anything it likes! ChrisA

On Mon, Oct 26, 2015 at 02:02:00AM -0400, Alexander Walters wrote:
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. Giving the function a name and module scope supports testing. But if you really want to get rid of it: del the_function after adding it to the dispatch table. 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: class Switch: def cheese(x): ... def spam(x): ... def eggs(x): ... dispatch = {} dispatch.update(Switch.__dict__) result = dispatch[key](arg) Or one could write a class decorator (or a metaclass) to post-process the class and returns whatever you like. If I were doing this a lot, I would invest the time in building a nice switch construct, before looking for new syntax.
Assign to *anything*? a, b, c = 1, 2, 3 def a, b, c(x, y): ... I don't see that, at least, working.
It's not clear to me. To me, it looks like you've badly mistyped a function annotation. If you hadn't explained what you wanted, I wouldn't have a clue what "def dispatch['foo'](bar)" meant. -- Steve

On Oct 26, 2015, at 06:45, Steven D'Aprano <steve@pearwood.info> wrote:
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.
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 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.
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.

On 10/26/2015 9:45 AM, Steven D'Aprano wrote:
dispatch = dict(Switch.__dict__) Except for the extraneous dunder names in __dict__, I like this. For the fastidious, dispatch = {k:v for k, v in Switch.__dict__.items() if k[0] != '_'} This could be wrapped in a function that would take either a module (globals()) or class (.__dict__) as the namespace source. -- Terry Jan Reedy

You can just do: dispatch = {} def dispatcher(f): dispatch[f.__name__] = f @dispatcher def foo(bar): return bar * bar I really think that might be weird and kind of jarring to read... On October 26, 2015 1:02:00 AM CDT, Alexander Walters <tritium-list@sdamon.com> wrote:
-- Sent from my Nexus 5 with K-9 Mail. Please excuse my brevity.
participants (9)
-
Alexander Walters
-
Andrew Barnert
-
Bruce Leban
-
Chris Angelico
-
Ian Kelly
-
MRAB
-
Ryan Gonzalez
-
Steven D'Aprano
-
Terry Reedy