On Fri, Feb 28, 2014 at 10:38 PM, Marko Rauhamaa <ma...@pacujo.net> wrote: > Chris Angelico <ros...@gmail.com>: > >> Python currently has dispatch tables and if/elif chains, and a strong >> cultural aversion to switch. You could change that by coming up with >> some *really* awesome proposal, but you'll be fighting against the >> tide a bit. > > It's easy have a "cultural aversion" when the language doesn't provide > the facility.
I'm talking about the strong resistance that gets put up any time the suggestion comes up on python-ideas or somesuch. The core devs and Guido especially are against the idea. > Switch statements provide for excellent readability in parsers and state > machines, for example. They also allow the Python compiler to optimize > the statement internally unlike long if-else chains. It's usually possible to turn any concrete example of a switch statement into an equally-readable dispatch table. All you need is for your search terms to be hashable (which is a much less stringent requirement than is usually put on switch blocks, like "must be machine word signed integer"), and you can do something like this: compare_key = { # Same target(s). ast.Assign: lambda node: ' '.join(dump(t) for t in node.targets), # Same target and same operator. ast.AugAssign: lambda node: dump(node.target) + dump(node.op) + "=", # A return statement is always compatible with another. ast.Return: lambda node: "(easy)", # Calling these never compatible is wrong. Calling them # always compatible will give lots of false positives. ast.Expr: lambda node: "(maybe)", # These ones are never compatible, so return some # object that's never equal to anything. ast.Import: lambda node: float("nan"), ast.ImportFrom: lambda node: float("nan"), ast.Pass: lambda node: float("nan"), ast.Raise: lambda node: float("nan"), ast.If: lambda node: float("nan"), } I then effectively do a big switch block like this: if try_type not in compare_key: print("Unrecognized type",try_type.__name__,file=sys.stderr) compare_key[try_type] = lambda node: float("nan") func = compare_key[try_type] try_node = func(node.body[0]) for handler in node.handlers: if try_node != func(handler.body[0]): return The first check (the "not in" bit) is kinda like a default clause, but it makes the output only once for any given type. For a more 'true' default clause, I could do this: try: func = compare_key[try_type] except KeyError: func = compare_key["default"] but I take advantage of the fact that the dispatch table is a dict and mutate it. Also, if this were done in a switch block, there'd be some redundancy. I call the same function on node.body[0] and on handler.body[0] for each handler in handlers, so there's structure that's common to all the branches there. I'm not sure how, with a classic C-style switch block, I could implement that cleanly. Probably I'd end up using function pointers and basically doing it exactly the same way :) The only major thing C's switch does that a dispatch table doesn't is fall-through. And let's face it, if your big argument in favour of a switch statement is "I need fall-through", you're not just going to have Python devs against you, you're also going to fight against the roughly 50% of C programmers who detest that feature :) (FWIW, I'm in the other 50%. I quite like fall-through, and there are times when it's a very clean way to express something. But even those cases can usually be expressed one way or another with only a little more redundancy - for instance, have one case that sets the key to be the next one, and then have stand-alone if blocks rather than if/elif. Considering that that use of fall-through usually requires an explanatory comment anyway, you're not really losing much.) So, going back to your statement: > Switch statements provide for excellent readability in parsers and state > machines, for example. The best way to start trying to build support for this would be to mock up a syntax for a switch statement, and find a currently-existing parser or state machine to translate. Show us the "before and after" shots. That's what I'm currently doing to justify an exception expression syntax - examples like this: pwd = (os.getcwd() except OSError: None) # Lib/tkinter/filedialog.py:210: try: pwd = os.getcwd() except OSError: pwd = None g = (grp.getgrnam(tarinfo.gname)[2] except KeyError: tarinfo.gid) u = (pwd.getpwnam(tarinfo.uname)[2] except KeyError: tarinfo.uid) # Lib/tarfile.py:2198: try: g = grp.getgrnam(tarinfo.gname)[2] except KeyError: g = tarinfo.gid try: u = pwd.getpwnam(tarinfo.uname)[2] except KeyError: u = tarinfo.uid This is real Python code, straight out of the standard library. I don't know if you could find many examples in the stdlib that beg for a switch statement, but pick up some real-world code of your own, or from some open source project, or something. Even if, in practice, it wouldn't be changed for many years (because that project needs to be compatible with previous versions of Python), it'd be worth showing "here's what could be". And then be prepared for a few hundred posts' worth of bikeshedding about the details :) ChrisA -- https://mail.python.org/mailman/listinfo/python-list