A more generalized switch statement for Python?
Many languages have a "switch" (or "case") statement to handle branching to different blocks based on the value of a particular expression. Python, however, does not have a construct of that sort; it relies on chains of if/elif/else to effect similar functionality. But there have been calls to add the construct over the years. A recent discussion on the python-ideas mailing list demonstrates some of the thinking about what a Python switch might look like—it also serves to give a look at the open language-design process that typifies the language.
There are two PEPs that have proposed the feature over the years:
Marc-André Lemburg's PEP 275 from 2001
and Python benevolent dictator for life Guido van Rossum's PEP 3103 from
2006. The latter came about due to some differences of opinion about
the behavior of the feature and how it would be implemented. Ultimately,
though, both were rejected based on an informal poll: "A quick poll
during my keynote presentation at PyCon 2007 shows this proposal has no
popular support. I therefore reject it.
", Van Rossum said in his PEP.
In a discussion on type hinting for the pathlib module (and related standard library routines that can use and return either str or bytes types), the lack of a Python switch reared its head again. That initial thread centered around using the AnyStr annotation for the __fspath__() protocol, which can return either bytes or str. In a post in that thread, Van Rossum mused about adding a switch statement, though he called it "match":
That post generated a few responses in favor of looking at the feature, so
Van Rossum soon started a new "match statement
brainstorm" thread. He said that Python might well benefit from moving
beyond the "pre-computed lookup table
" approach that both of
the earlier PEPs had taken and learn from what other languages have done
(notably, Haskell).
A Python match statement (though he used a
switch keyword in his examples) could possibly do quite a bit more than
had been envisioned earlier:
- match by value or set of values (like those PEPs)
- match by type (isinstance() checks)
- match on tuple structure, including nesting and * unpacking (essentially, try a series of destructuring assignments until one works)
- match on dict structure? (extension of destructuring to dicts)
- match on instance variables or attributes by name?
- match on generalized condition (predicate)?
The idea of "destructuring" is to pull out the values in a composite type (such as a tuple), either using positional operators or using attribute names for types like the collections.namedtuple type. That could potentially be extended to destructure dictionaries by key name or, perhaps, positionally.
His post had some "strawman syntax
" for how tuple
destructuring in a switch statement might work, along with a
"demonstration" of how it would operate given different kinds of input:
def demo(arg):
switch arg:
case (x=p, y=q): print('x=', p, 'y=', q)
case (a, b, *_): print('a=', a, 'b=', b)
else: print('Too bad')
Taking his example further, Van Rossum showed how it all might work:
Point = namedtuple('Point', 'x y z')
and some variables like this:
a = Point(x=1, y=2, z=3) b = (1, 2, 3, 4) c = 'hola' d = 42then we could call demo with these variables:
>>> demo(a) x= 1 y= 2 >>> demo(b) a= 1 b= 2 >>> demo(c) a= h b= o >>> demo(d) Too bad
He did note the "slightly unfortunate outcome
" for the string
(since strings are treated as sequences of one-character strings in Python).
As might be guessed, that strawman syntax led to some other suggestions. Several commented on the attribute-extraction case with its odd-looking "assignment" construct. Nick Coghlan suggested an alternate formulation:
[...]
case (.x as p, .y as q): print('x=', p, 'y=', q)
In the brainstorming post, Van Rossum had also challenged others "to fit simple value equality, set membership,
isinstance, and guards into that same syntax.
" Coghlan had some
ideas on that, but he also suggested a new operator, of sorts:
switch expr as arg:
case ?= (.x as p, .y as q): print('x=', p, 'y=', q)
case ?= (a, b, *_): print('a=', a, 'b=', b)
case arg == value: ...
case lower_bound <= arg <= upper_bound: ...
case arg in container: ...
else: print('Too bad')
(.x as p, .y as q) = expr
In a similar vein, item unpacking might look like:
(["x"] as p, ["y"] as q) = expr
Franklin Lee also had an extensive set of suggestions, but several in the thread thought some of them were overkill. Paul Moore suggested allowing an arbitrary expression for the switch that would be given a name for use in the case statements (syntax that Coghlan also adopted):
switch expr as name:
But Joao S. O. Bueno had a fundamental
concern about the need to add a switch at all: "I still fail to see what justifies violating The One Obvious Way to Do It which
uses an if/elif sequence
". Van Rossum
agreed to a certain extent, but noted that
there are a number of match operations that are difficult to write using
if statements. For example:
There might be some other interesting possibilities when combining matching
with type annotations, he said. Overall, though, "it's about the
most speculative piece of language design I've
contemplated in a long time
".
Michael Selik noted that many of the matching features are already available for if statements. The missing piece is something like the ?= operator to allow trying the destructure operations without causing an exception if they fail—they would simply return false. He provided an example:
def demo(arg):
if p, q ?= arg.x, arg.y: # dict structure
elif x ?= arg.x and isinstance(x, int) # assignment + guard
elif a, b, *_ ?= arg: # tuple structure
elif isinstance(arg, Mapping): # nothing new here
rejected by this group in the past for other conditionalisms".
But Greg Ewing (and others) were not
particularly pleased with the suggestion: "the above looks like
an unreadable mess to me
". The problem, as Moore described, is that switch is a focused
operation on a single subject, while if statements are not:
With a switch statement, however, the subject is stated once, at the top of the statement. The checks are then listed one after the other, and they are all by definition checks against the subject expression.
There was more discussion of the ideas, though no real conclusions were drawn. No one reported an in-progress PEP to the list, so there may be no one who feels strongly enough about the feature to take that step. But it is an idea that has recurred in Python circles over the years, so it will not be a surprise to see it pop up again sooner or later. In the meantime, as with many discussions on python-ideas, we get a look inside the thinking of the Python core developers.
