733 lines
44 KiB
HTML
733 lines
44 KiB
HTML
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<meta name="color-scheme" content="light dark">
|
||
<title>PEP 3103 – A Switch/Case Statement | peps.python.org</title>
|
||
<link rel="shortcut icon" href="../_static/py.png">
|
||
<link rel="canonical" href="https://peps.python.org/pep-3103/">
|
||
<link rel="stylesheet" href="../_static/style.css" type="text/css">
|
||
<link rel="stylesheet" href="../_static/mq.css" type="text/css">
|
||
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" media="(prefers-color-scheme: light)" id="pyg-light">
|
||
<link rel="stylesheet" href="../_static/pygments_dark.css" type="text/css" media="(prefers-color-scheme: dark)" id="pyg-dark">
|
||
<link rel="alternate" type="application/rss+xml" title="Latest PEPs" href="https://peps.python.org/peps.rss">
|
||
<meta property="og:title" content='PEP 3103 – A Switch/Case Statement | peps.python.org'>
|
||
<meta property="og:description" content="Python-dev has recently seen a flurry of discussion on adding a switch statement. In this PEP I’m trying to extract my own preferences from the smorgasbord of proposals, discussing alternatives and explaining my choices where I can. I’ll also indicate...">
|
||
<meta property="og:type" content="website">
|
||
<meta property="og:url" content="https://peps.python.org/pep-3103/">
|
||
<meta property="og:site_name" content="Python Enhancement Proposals (PEPs)">
|
||
<meta property="og:image" content="https://peps.python.org/_static/og-image.png">
|
||
<meta property="og:image:alt" content="Python PEPs">
|
||
<meta property="og:image:width" content="200">
|
||
<meta property="og:image:height" content="200">
|
||
<meta name="description" content="Python-dev has recently seen a flurry of discussion on adding a switch statement. In this PEP I’m trying to extract my own preferences from the smorgasbord of proposals, discussing alternatives and explaining my choices where I can. I’ll also indicate...">
|
||
<meta name="theme-color" content="#3776ab">
|
||
</head>
|
||
<body>
|
||
|
||
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
|
||
<symbol id="svg-sun-half" viewBox="0 0 24 24" pointer-events="all">
|
||
<title>Following system colour scheme</title>
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
|
||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<circle cx="12" cy="12" r="9"></circle>
|
||
<path d="M12 3v18m0-12l4.65-4.65M12 14.3l7.37-7.37M12 19.6l8.85-8.85"></path>
|
||
</svg>
|
||
</symbol>
|
||
<symbol id="svg-moon" viewBox="0 0 24 24" pointer-events="all">
|
||
<title>Selected dark colour scheme</title>
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
|
||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||
<path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z"></path>
|
||
</svg>
|
||
</symbol>
|
||
<symbol id="svg-sun" viewBox="0 0 24 24" pointer-events="all">
|
||
<title>Selected light colour scheme</title>
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
|
||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<circle cx="12" cy="12" r="5"></circle>
|
||
<line x1="12" y1="1" x2="12" y2="3"></line>
|
||
<line x1="12" y1="21" x2="12" y2="23"></line>
|
||
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
|
||
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
|
||
<line x1="1" y1="12" x2="3" y2="12"></line>
|
||
<line x1="21" y1="12" x2="23" y2="12"></line>
|
||
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
|
||
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
|
||
</svg>
|
||
</symbol>
|
||
</svg>
|
||
<script>
|
||
|
||
document.documentElement.dataset.colour_scheme = localStorage.getItem("colour_scheme") || "auto"
|
||
</script>
|
||
<section id="pep-page-section">
|
||
<header>
|
||
<h1>Python Enhancement Proposals</h1>
|
||
<ul class="breadcrumbs">
|
||
<li><a href="https://www.python.org/" title="The Python Programming Language">Python</a> » </li>
|
||
<li><a href="../pep-0000/">PEP Index</a> » </li>
|
||
<li>PEP 3103</li>
|
||
</ul>
|
||
<button id="colour-scheme-cycler" onClick="setColourScheme(nextColourScheme())">
|
||
<svg aria-hidden="true" class="colour-scheme-icon-when-auto"><use href="#svg-sun-half"></use></svg>
|
||
<svg aria-hidden="true" class="colour-scheme-icon-when-dark"><use href="#svg-moon"></use></svg>
|
||
<svg aria-hidden="true" class="colour-scheme-icon-when-light"><use href="#svg-sun"></use></svg>
|
||
<span class="visually-hidden">Toggle light / dark / auto colour theme</span>
|
||
</button>
|
||
</header>
|
||
<article>
|
||
<section id="pep-content">
|
||
<h1 class="page-title">PEP 3103 – A Switch/Case Statement</h1>
|
||
<dl class="rfc2822 field-list simple">
|
||
<dt class="field-odd">Author<span class="colon">:</span></dt>
|
||
<dd class="field-odd">Guido van Rossum <guido at python.org></dd>
|
||
<dt class="field-even">Status<span class="colon">:</span></dt>
|
||
<dd class="field-even"><abbr title="Formally declined and will not be accepted">Rejected</abbr></dd>
|
||
<dt class="field-odd">Type<span class="colon">:</span></dt>
|
||
<dd class="field-odd"><abbr title="Normative PEP with a new feature for Python, implementation change for CPython or interoperability standard for the ecosystem">Standards Track</abbr></dd>
|
||
<dt class="field-even">Created<span class="colon">:</span></dt>
|
||
<dd class="field-even">25-Jun-2006</dd>
|
||
<dt class="field-odd">Python-Version<span class="colon">:</span></dt>
|
||
<dd class="field-odd">3.0</dd>
|
||
<dt class="field-even">Post-History<span class="colon">:</span></dt>
|
||
<dd class="field-even">26-Jun-2006</dd>
|
||
</dl>
|
||
<hr class="docutils" />
|
||
<section id="contents">
|
||
<details><summary>Table of Contents</summary><ul class="simple">
|
||
<li><a class="reference internal" href="#rejection-notice">Rejection Notice</a></li>
|
||
<li><a class="reference internal" href="#abstract">Abstract</a></li>
|
||
<li><a class="reference internal" href="#rationale">Rationale</a></li>
|
||
<li><a class="reference internal" href="#basic-syntax">Basic Syntax</a><ul>
|
||
<li><a class="reference internal" href="#alternative-1">Alternative 1</a></li>
|
||
<li><a class="reference internal" href="#alternative-2">Alternative 2</a></li>
|
||
<li><a class="reference internal" href="#alternative-3">Alternative 3</a></li>
|
||
<li><a class="reference internal" href="#alternative-4">Alternative 4</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#extended-syntax">Extended Syntax</a><ul>
|
||
<li><a class="reference internal" href="#alternative-a">Alternative A</a></li>
|
||
<li><a class="reference internal" href="#alternative-b">Alternative B</a></li>
|
||
<li><a class="reference internal" href="#alternative-c">Alternative C</a></li>
|
||
<li><a class="reference internal" href="#alternative-d">Alternative D</a></li>
|
||
<li><a class="reference internal" href="#discussion">Discussion</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#semantics">Semantics</a><ul>
|
||
<li><a class="reference internal" href="#if-elif-chain-vs-dict-based-dispatch">If/Elif Chain vs. Dict-based Dispatch</a></li>
|
||
<li><a class="reference internal" href="#when-to-freeze-the-dispatch-dict">When to Freeze the Dispatch Dict</a><ul>
|
||
<li><a class="reference internal" href="#option-1">Option 1</a></li>
|
||
<li><a class="reference internal" href="#option-2">Option 2</a></li>
|
||
<li><a class="reference internal" href="#option-3">Option 3</a></li>
|
||
<li><a class="reference internal" href="#option-4">Option 4</a></li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#conclusion">Conclusion</a></li>
|
||
<li><a class="reference internal" href="#copyright">Copyright</a></li>
|
||
</ul>
|
||
</details></section>
|
||
<section id="rejection-notice">
|
||
<h2><a class="toc-backref" href="#rejection-notice" role="doc-backlink">Rejection Notice</a></h2>
|
||
<p>A quick poll during my keynote presentation at PyCon 2007 shows this
|
||
proposal has no popular support. I therefore reject it.</p>
|
||
</section>
|
||
<section id="abstract">
|
||
<h2><a class="toc-backref" href="#abstract" role="doc-backlink">Abstract</a></h2>
|
||
<p>Python-dev has recently seen a flurry of discussion on adding a switch
|
||
statement. In this PEP I’m trying to extract my own preferences from
|
||
the smorgasbord of proposals, discussing alternatives and explaining
|
||
my choices where I can. I’ll also indicate how strongly I feel about
|
||
alternatives I discuss.</p>
|
||
<p>This PEP should be seen as an alternative to <a class="pep reference internal" href="../pep-0275/" title="PEP 275 – Switching on Multiple Values">PEP 275</a>. My views are
|
||
somewhat different from that PEP’s author, but I’m grateful for the
|
||
work done in that PEP.</p>
|
||
<p>This PEP introduces canonical names for the many variants that have
|
||
been discussed for different aspects of the syntax and semantics, such
|
||
as “alternative 1”, “school II”, “option 3” and so on. Hopefully
|
||
these names will help the discussion.</p>
|
||
</section>
|
||
<section id="rationale">
|
||
<h2><a class="toc-backref" href="#rationale" role="doc-backlink">Rationale</a></h2>
|
||
<p>A common programming idiom is to consider an expression and do
|
||
different things depending on its value. This is usually done with a
|
||
chain of if/elif tests; I’ll refer to this form as the “if/elif
|
||
chain”. There are two main motivations to want to introduce new
|
||
syntax for this idiom:</p>
|
||
<ul class="simple">
|
||
<li>It is repetitive: the variable and the test operator, usually ‘==’
|
||
or ‘in’, are repeated in each if/elif branch.</li>
|
||
<li>It is inefficient: when an expression matches the last test value
|
||
(or no test value at all) it is compared to each of the preceding
|
||
test values.</li>
|
||
</ul>
|
||
<p>Both of these complaints are relatively mild; there isn’t a lot of
|
||
readability or performance to be gained by writing this differently.
|
||
Yet, some kind of switch statement is found in many languages and it
|
||
is not unreasonable to expect that its addition to Python will allow
|
||
us to write up certain code more cleanly and efficiently than before.</p>
|
||
<p>There are forms of dispatch that are not suitable for the proposed
|
||
switch statement; for example, when the number of cases is not
|
||
statically known, or when it is desirable to place the code for
|
||
different cases in different classes or files.</p>
|
||
</section>
|
||
<section id="basic-syntax">
|
||
<h2><a class="toc-backref" href="#basic-syntax" role="doc-backlink">Basic Syntax</a></h2>
|
||
<p>I’m considering several variants of the syntax first proposed in PEP
|
||
275 here. There are lots of other possibilities, but I don’t see that
|
||
they add anything.</p>
|
||
<p>I’ve recently been converted to alternative 1.</p>
|
||
<p>I should note that all alternatives here have the “implicit break”
|
||
property: at the end of the suite for a particular case, the control
|
||
flow jumps to the end of the whole switch statement. There is no way
|
||
to pass control from one case to another. This in contrast to C,
|
||
where an explicit ‘break’ statement is required to prevent falling
|
||
through to the next case.</p>
|
||
<p>In all alternatives, the else-suite is optional. It is more Pythonic
|
||
to use ‘else’ here rather than introducing a new reserved word,
|
||
‘default’, as in C.</p>
|
||
<p>Semantics are discussed in the next top-level section.</p>
|
||
<section id="alternative-1">
|
||
<h3><a class="toc-backref" href="#alternative-1" role="doc-backlink">Alternative 1</a></h3>
|
||
<p>This is the preferred form in <a class="pep reference internal" href="../pep-0275/" title="PEP 275 – Switching on Multiple Values">PEP 275</a>:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">switch</span> <span class="n">EXPR</span><span class="p">:</span>
|
||
<span class="k">case</span> <span class="n">EXPR</span><span class="p">:</span>
|
||
<span class="n">SUITE</span>
|
||
<span class="k">case</span> <span class="n">EXPR</span><span class="p">:</span>
|
||
<span class="n">SUITE</span>
|
||
<span class="o">...</span>
|
||
<span class="k">else</span><span class="p">:</span>
|
||
<span class="n">SUITE</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>The main downside is that the suites where all the action is are
|
||
indented two levels deep; this can be remedied by indenting the cases
|
||
“half a level” (e.g. 2 spaces if the general indentation level is 4).</p>
|
||
</section>
|
||
<section id="alternative-2">
|
||
<h3><a class="toc-backref" href="#alternative-2" role="doc-backlink">Alternative 2</a></h3>
|
||
<p>This is Fredrik Lundh’s preferred form; it differs by not indenting
|
||
the cases:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">switch</span> <span class="n">EXPR</span><span class="p">:</span>
|
||
<span class="k">case</span> <span class="n">EXPR</span><span class="p">:</span>
|
||
<span class="n">SUITE</span>
|
||
<span class="k">case</span> <span class="n">EXPR</span><span class="p">:</span>
|
||
<span class="n">SUITE</span>
|
||
<span class="o">....</span>
|
||
<span class="k">else</span><span class="p">:</span>
|
||
<span class="n">SUITE</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Some reasons not to choose this include expected difficulties for
|
||
auto-indenting editors, folding editors, and the like; and confused
|
||
users. There are no situations currently in Python where a line
|
||
ending in a colon is followed by an unindented line.</p>
|
||
</section>
|
||
<section id="alternative-3">
|
||
<h3><a class="toc-backref" href="#alternative-3" role="doc-backlink">Alternative 3</a></h3>
|
||
<p>This is the same as alternative 2 but leaves out the colon after the
|
||
switch:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">switch</span> <span class="n">EXPR</span>
|
||
<span class="k">case</span> <span class="n">EXPR</span><span class="p">:</span>
|
||
<span class="n">SUITE</span>
|
||
<span class="k">case</span> <span class="n">EXPR</span><span class="p">:</span>
|
||
<span class="n">SUITE</span>
|
||
<span class="o">....</span>
|
||
<span class="k">else</span><span class="p">:</span>
|
||
<span class="n">SUITE</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>The hope of this alternative is that it will not upset the auto-indent
|
||
logic of the average Python-aware text editor less. But it looks
|
||
strange to me.</p>
|
||
</section>
|
||
<section id="alternative-4">
|
||
<h3><a class="toc-backref" href="#alternative-4" role="doc-backlink">Alternative 4</a></h3>
|
||
<p>This leaves out the ‘case’ keyword on the basis that it is redundant:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">switch</span> <span class="n">EXPR</span><span class="p">:</span>
|
||
<span class="n">EXPR</span><span class="p">:</span>
|
||
<span class="n">SUITE</span>
|
||
<span class="n">EXPR</span><span class="p">:</span>
|
||
<span class="n">SUITE</span>
|
||
<span class="o">...</span>
|
||
<span class="k">else</span><span class="p">:</span>
|
||
<span class="n">SUITE</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Unfortunately now we are forced to indent the case expressions,
|
||
because otherwise (at least in the absence of an ‘else’ keyword) the
|
||
parser would have a hard time distinguishing between an unindented
|
||
case expression (which continues the switch statement) or an unrelated
|
||
statement that starts like an expression (such as an assignment or a
|
||
procedure call). The parser is not smart enough to backtrack once it
|
||
sees the colon. This is my least favorite alternative.</p>
|
||
</section>
|
||
</section>
|
||
<section id="extended-syntax">
|
||
<h2><a class="toc-backref" href="#extended-syntax" role="doc-backlink">Extended Syntax</a></h2>
|
||
<p>There is one additional concern that needs to be addressed
|
||
syntactically. Often two or more values need to be treated the same.
|
||
In C, this done by writing multiple case labels together without any
|
||
code between them. The “fall through” semantics then mean that these
|
||
are all handled by the same code. Since the Python switch will not
|
||
have fall-through semantics (which have yet to find a champion) we
|
||
need another solution. Here are some alternatives.</p>
|
||
<section id="alternative-a">
|
||
<h3><a class="toc-backref" href="#alternative-a" role="doc-backlink">Alternative A</a></h3>
|
||
<p>Use:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">case</span> <span class="n">EXPR</span><span class="p">:</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>to match on a single expression; use:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">case</span> <span class="n">EXPR</span><span class="p">,</span> <span class="n">EXPR</span><span class="p">,</span> <span class="o">...</span><span class="p">:</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>to match on multiple expressions. The is interpreted so that if EXPR
|
||
is a parenthesized tuple or another expression whose value is a tuple,
|
||
the switch expression must equal that tuple, not one of its elements.
|
||
This means that we cannot use a variable to indicate multiple cases.
|
||
While this is also true in C’s switch statement, it is a relatively
|
||
common occurrence in Python (see for example sre_compile.py).</p>
|
||
</section>
|
||
<section id="alternative-b">
|
||
<h3><a class="toc-backref" href="#alternative-b" role="doc-backlink">Alternative B</a></h3>
|
||
<p>Use:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">case</span> <span class="n">EXPR</span><span class="p">:</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>to match on a single expression; use:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">case</span> <span class="ow">in</span> <span class="n">EXPR_LIST</span><span class="p">:</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>to match on multiple expressions. If EXPR_LIST is a single
|
||
expression, the ‘in’ forces its interpretation as an iterable (or
|
||
something supporting __contains__, in a minority semantics
|
||
alternative). If it is multiple expressions, each of those is
|
||
considered for a match.</p>
|
||
</section>
|
||
<section id="alternative-c">
|
||
<h3><a class="toc-backref" href="#alternative-c" role="doc-backlink">Alternative C</a></h3>
|
||
<p>Use:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">case</span> <span class="n">EXPR</span><span class="p">:</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>to match on a single expression; use:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">case</span> <span class="n">EXPR</span><span class="p">,</span> <span class="n">EXPR</span><span class="p">,</span> <span class="o">...</span><span class="p">:</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>to match on multiple expressions (as in alternative A); and use:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">case</span> <span class="o">*</span><span class="n">EXPR</span><span class="p">:</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>to match on the elements of an expression whose value is an iterable.
|
||
The latter two cases can be combined, so that the true syntax is more
|
||
like this:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">case</span> <span class="p">[</span><span class="o">*</span><span class="p">]</span><span class="n">EXPR</span><span class="p">,</span> <span class="p">[</span><span class="o">*</span><span class="p">]</span><span class="n">EXPR</span><span class="p">,</span> <span class="o">...</span><span class="p">:</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>The <code class="docutils literal notranslate"><span class="pre">*</span></code> notation is similar to the use of prefix <code class="docutils literal notranslate"><span class="pre">*</span></code> already in use for
|
||
variable-length parameter lists and for passing computed argument
|
||
lists, and often proposed for value-unpacking (e.g. <code class="docutils literal notranslate"><span class="pre">a,</span> <span class="pre">b,</span> <span class="pre">*c</span> <span class="pre">=</span> <span class="pre">X</span></code> as
|
||
an alternative to <code class="docutils literal notranslate"><span class="pre">(a,</span> <span class="pre">b),</span> <span class="pre">c</span> <span class="pre">=</span> <span class="pre">X[:2],</span> <span class="pre">X[2:]</span></code>).</p>
|
||
</section>
|
||
<section id="alternative-d">
|
||
<h3><a class="toc-backref" href="#alternative-d" role="doc-backlink">Alternative D</a></h3>
|
||
<p>This is a mixture of alternatives B and C; the syntax is like
|
||
alternative B but instead of the ‘in’ keyword it uses ‘*’. This is
|
||
more limited, but still allows the same flexibility. It uses:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">case</span> <span class="n">EXPR</span><span class="p">:</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>to match on a single expression and:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">case</span> <span class="o">*</span><span class="n">EXPR</span><span class="p">:</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>to match on the elements of an iterable. If one wants to specify
|
||
multiple matches in one case, one can write this:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">case</span> <span class="o">*</span><span class="p">(</span><span class="n">EXPR</span><span class="p">,</span> <span class="n">EXPR</span><span class="p">,</span> <span class="o">...</span><span class="p">):</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>or perhaps this (although it’s a bit strange because the relative
|
||
priority of ‘*’ and ‘,’ is different than elsewhere):</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">case</span> <span class="o">*</span> <span class="n">EXPR</span><span class="p">,</span> <span class="n">EXPR</span><span class="p">,</span> <span class="o">...</span><span class="p">:</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="discussion">
|
||
<h3><a class="toc-backref" href="#discussion" role="doc-backlink">Discussion</a></h3>
|
||
<p>Alternatives B, C and D are motivated by the desire to specify
|
||
multiple cases with the same treatment using a variable representing a
|
||
set (usually a tuple) rather than spelling them out. The motivation
|
||
for this is usually that if one has several switches over the same set
|
||
of cases it’s a shame to have to spell out all the alternatives each
|
||
time. An additional motivation is to be able to specify <em>ranges</em> to
|
||
be matched easily and efficiently, similar to Pascal’s “1..1000:”
|
||
notation. At the same time we want to prevent the kind of mistake
|
||
that is common in exception handling (and which will be addressed in
|
||
Python 3000 by changing the syntax of the except clause): writing
|
||
“case 1, 2:” where “case (1, 2):” was meant, or vice versa.</p>
|
||
<p>The case could be made that the need is insufficient for the added
|
||
complexity; C doesn’t have a way to express ranges either, and it’s
|
||
used a lot more than Pascal these days. Also, if a dispatch method
|
||
based on dict lookup is chosen as the semantics, large ranges could be
|
||
inefficient (consider range(1, sys.maxint)).</p>
|
||
<p>All in all my preferences are (from most to least favorite) B, A, D’,
|
||
C, where D’ is D without the third possibility.</p>
|
||
</section>
|
||
</section>
|
||
<section id="semantics">
|
||
<h2><a class="toc-backref" href="#semantics" role="doc-backlink">Semantics</a></h2>
|
||
<p>There are several issues to review before we can choose the right
|
||
semantics.</p>
|
||
<section id="if-elif-chain-vs-dict-based-dispatch">
|
||
<h3><a class="toc-backref" href="#if-elif-chain-vs-dict-based-dispatch" role="doc-backlink">If/Elif Chain vs. Dict-based Dispatch</a></h3>
|
||
<p>There are several main schools of thought about the switch statement’s
|
||
semantics:</p>
|
||
<ul class="simple">
|
||
<li>School I wants to define the switch statement in term of an
|
||
equivalent if/elif chain (possibly with some optimization thrown
|
||
in).</li>
|
||
<li>School II prefers to think of it as a dispatch on a precomputed
|
||
dict. There are different choices for when the precomputation
|
||
happens.</li>
|
||
<li>There’s also school III, which agrees with school I that the
|
||
definition of a switch statement should be in terms of an equivalent
|
||
if/elif chain, but concedes to the optimization camp that all
|
||
expressions involved must be hashable.</li>
|
||
</ul>
|
||
<p>We need to further separate school I into school Ia and school Ib:</p>
|
||
<ul class="simple">
|
||
<li>School Ia has a simple position: a switch statement is translated to
|
||
an equivalent if/elif chain, and that’s that. It should not be
|
||
linked to optimization at all. That is also my main objection
|
||
against this school: without any hint of optimization, the switch
|
||
statement isn’t attractive enough to warrant new syntax.</li>
|
||
<li>School Ib has a more complex position: it agrees with school II that
|
||
optimization is important, and is willing to concede the compiler
|
||
certain liberties to allow this. (For example, <a class="pep reference internal" href="../pep-0275/" title="PEP 275 – Switching on Multiple Values">PEP 275</a> Solution 1.)
|
||
In particular, hash() of the switch and case expressions may or may
|
||
not be called (so it should be side-effect-free); and the case
|
||
expressions may not be evaluated each time as expected by the
|
||
if/elif chain behavior, so the case expressions should also be
|
||
side-effect free. My objection to this (elaborated below) is that
|
||
if either the hash() or the case expressions aren’t
|
||
side-effect-free, optimized and unoptimized code may behave
|
||
differently.</li>
|
||
</ul>
|
||
<p>School II grew out of the realization that optimization of commonly
|
||
found cases isn’t so easy, and that it’s better to face this head on.
|
||
This will become clear below.</p>
|
||
<p>The differences between school I (mostly school Ib) and school II are
|
||
threefold:</p>
|
||
<ul class="simple">
|
||
<li>When optimizing using a dispatch dict, if either the switch
|
||
expression or the case expressions are unhashable (in which case
|
||
hash() raises an exception), school Ib requires catching the hash()
|
||
failure and falling back to an if/elif chain. School II simply lets
|
||
the exception happen. The problem with catching an exception in
|
||
hash() as required by school Ib, is that this may hide a genuine
|
||
bug. A possible way out is to only use a dispatch dict if all case
|
||
expressions are ints, strings or other built-ins with known good
|
||
hash behavior, and to only attempt to hash the switch expression if
|
||
it is also one of those types. Type objects should probably also be
|
||
supported here. This is the (only) problem that school III
|
||
addresses.</li>
|
||
<li>When optimizing using a dispatch dict, if the hash() function of any
|
||
expression involved returns an incorrect value, under school Ib,
|
||
optimized code will not behave the same as unoptimized code. This
|
||
is a well-known problem with optimization-related bugs, and waste
|
||
lots of developer time. Under school II, in this situation
|
||
incorrect results are produced at least consistently, which should
|
||
make debugging a bit easier. The way out proposed for the previous
|
||
bullet would also help here.</li>
|
||
<li>School Ib doesn’t have a good optimization strategy if the case
|
||
expressions are named constants. The compiler cannot know their
|
||
values for sure, and it cannot know whether they are truly constant.
|
||
As a way out, it has been proposed to re-evaluate the expression
|
||
corresponding to the case once the dict has identified which case
|
||
should be taken, to verify that the value of the expression didn’t
|
||
change. But strictly speaking, all the case expressions occurring
|
||
before that case would also have to be checked, in order to preserve
|
||
the true if/elif chain semantics, thereby completely killing the
|
||
optimization. Another proposed solution is to have callbacks
|
||
notifying the dispatch dict of changes in the value of variables or
|
||
attributes involved in the case expressions. But this is not likely
|
||
implementable in the general case, and would require many namespaces
|
||
to bear the burden of supporting such callbacks, which currently
|
||
don’t exist at all.</li>
|
||
<li>Finally, there’s a difference of opinion regarding the treatment of
|
||
duplicate cases (i.e. two or more cases with match expressions that
|
||
evaluates to the same value). School I wants to treat this the same
|
||
is an if/elif chain would treat it (i.e. the first match wins and
|
||
the code for the second match is silently unreachable); school II
|
||
wants this to be an error at the time the dispatch dict is frozen
|
||
(so dead code doesn’t go undiagnosed).</li>
|
||
</ul>
|
||
<p>School I sees trouble in school II’s approach of pre-freezing a
|
||
dispatch dict because it places a new and unusual burden on
|
||
programmers to understand exactly what kinds of case values are
|
||
allowed to be frozen and when the case values will be frozen, or they
|
||
might be surprised by the switch statement’s behavior.</p>
|
||
<p>School II doesn’t believe that school Ia’s unoptimized switch is worth
|
||
the effort, and it sees trouble in school Ib’s proposal for
|
||
optimization, which can cause optimized and unoptimized code to behave
|
||
differently.</p>
|
||
<p>In addition, school II sees little value in allowing cases involving
|
||
unhashable values; after all if the user expects such values, they can
|
||
just as easily write an if/elif chain. School II also doesn’t believe
|
||
that it’s right to allow dead code due to overlapping cases to occur
|
||
unflagged, when the dict-based dispatch implementation makes it so
|
||
easy to trap this.</p>
|
||
<p>However, there are some use cases for overlapping/duplicate cases.
|
||
Suppose you’re switching on some OS-specific constants (e.g. exported
|
||
by the os module or some module like that). You have a case for each.
|
||
But on some OS, two different constants have the same value (since on
|
||
that OS they are implemented the same way – like O_TEXT and O_BINARY
|
||
on Unix). If duplicate cases are flagged as errors, your switch
|
||
wouldn’t work at all on that OS. It would be much better if you could
|
||
arrange the cases so that one case has preference over another.</p>
|
||
<p>There’s also the (more likely) use case where you have a set of cases
|
||
to be treated the same, but one member of the set must be treated
|
||
differently. It would be convenient to put the exception in an
|
||
earlier case and be done with it.</p>
|
||
<p>(Yes, it seems a shame not to be able to diagnose dead code due to
|
||
accidental case duplication. Maybe that’s less important, and
|
||
pychecker can deal with it? After all we don’t diagnose duplicate
|
||
method definitions either.)</p>
|
||
<p>This suggests school IIb: like school II but redundant cases must be
|
||
resolved by choosing the first match. This is trivial to implement
|
||
when building the dispatch dict (skip keys already present).</p>
|
||
<p>(An alternative would be to introduce new syntax to indicate “okay to
|
||
have overlapping cases” or “ok if this case is dead code” but I find
|
||
that overkill.)</p>
|
||
<p>Personally, I’m in school II: I believe that the dict-based dispatch
|
||
is the one true implementation for switch statements and that we
|
||
should face the limitations up front, so that we can reap maximal
|
||
benefits. I’m leaning towards school IIb – duplicate cases should be
|
||
resolved by the ordering of the cases instead of flagged as errors.</p>
|
||
</section>
|
||
<section id="when-to-freeze-the-dispatch-dict">
|
||
<h3><a class="toc-backref" href="#when-to-freeze-the-dispatch-dict" role="doc-backlink">When to Freeze the Dispatch Dict</a></h3>
|
||
<p>For the supporters of school II (dict-based dispatch), the next big
|
||
dividing issue is when to create the dict used for switching. I call
|
||
this “freezing the dict”.</p>
|
||
<p>The main problem that makes this interesting is the observation that
|
||
Python doesn’t have named compile-time constants. What is
|
||
conceptually a constant, such as re.IGNORECASE, is a variable to the
|
||
compiler, and there’s nothing to stop crooked code from modifying its
|
||
value.</p>
|
||
<section id="option-1">
|
||
<h4><a class="toc-backref" href="#option-1" role="doc-backlink">Option 1</a></h4>
|
||
<p>The most limiting option is to freeze the dict in the compiler. This
|
||
would require that the case expressions are all literals or
|
||
compile-time expressions involving only literals and operators whose
|
||
semantics are known to the compiler, since with the current state of
|
||
Python’s dynamic semantics and single-module compilation, there is no
|
||
hope for the compiler to know with sufficient certainty the values of
|
||
any variables occurring in such expressions. This is widely though
|
||
not universally considered too restrictive.</p>
|
||
<p>Raymond Hettinger is the main advocate of this approach. He proposes
|
||
a syntax where only a single literal of certain types is allowed as
|
||
the case expression. It has the advantage of being unambiguous and
|
||
easy to implement.</p>
|
||
<p>My main complaint about this is that by disallowing “named constants”
|
||
we force programmers to give up good habits. Named constants are
|
||
introduced in most languages to solve the problem of “magic numbers”
|
||
occurring in the source code. For example, sys.maxint is a lot more
|
||
readable than 2147483647. Raymond proposes to use string literals
|
||
instead of named “enums”, observing that the string literal’s content
|
||
can be the name that the constant would otherwise have. Thus, we
|
||
could write “case ‘IGNORECASE’:” instead of “case re.IGNORECASE:”.
|
||
However, if there is a spelling error in the string literal, the case
|
||
will silently be ignored, and who knows when the bug is detected. If
|
||
there is a spelling error in a NAME, however, the error will be caught
|
||
as soon as it is evaluated. Also, sometimes the constants are
|
||
externally defined (e.g. when parsing a file format like JPEG) and we
|
||
can’t easily choose appropriate string values. Using an explicit
|
||
mapping dict sounds like a poor hack.</p>
|
||
</section>
|
||
<section id="option-2">
|
||
<h4><a class="toc-backref" href="#option-2" role="doc-backlink">Option 2</a></h4>
|
||
<p>The oldest proposal to deal with this is to freeze the dispatch dict
|
||
the first time the switch is executed. At this point we can assume
|
||
that all the named “constants” (constant in the programmer’s mind,
|
||
though not to the compiler) used as case expressions are defined –
|
||
otherwise an if/elif chain would have little chance of success either.
|
||
Assuming the switch will be executed many times, doing some extra work
|
||
the first time pays back quickly by very quick dispatch times later.</p>
|
||
<p>An objection to this option is that there is no obvious object where
|
||
the dispatch dict can be stored. It can’t be stored on the code
|
||
object, which is supposed to be immutable; it can’t be stored on the
|
||
function object, since many function objects may be created for the
|
||
same function (e.g. for nested functions). In practice, I’m sure that
|
||
something can be found; it could be stored in a section of the code
|
||
object that’s not considered when comparing two code objects or when
|
||
pickling or marshalling a code object; or all switches could be stored
|
||
in a dict indexed by weak references to code objects. The solution
|
||
should also be careful not to leak switch dicts between multiple
|
||
interpreters.</p>
|
||
<p>Another objection is that the first-use rule allows obfuscated code
|
||
like this:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">):</span>
|
||
<span class="n">switch</span> <span class="n">x</span><span class="p">:</span>
|
||
<span class="k">case</span> <span class="n">y</span><span class="p">:</span>
|
||
<span class="nb">print</span> <span class="mi">42</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>To the untrained eye (not familiar with Python) this code would be
|
||
equivalent to this:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">):</span>
|
||
<span class="k">if</span> <span class="n">x</span> <span class="o">==</span> <span class="n">y</span><span class="p">:</span>
|
||
<span class="nb">print</span> <span class="mi">42</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>but that’s not what it does (unless it is always called with the same
|
||
value as the second argument). This has been addressed by suggesting
|
||
that the case expressions should not be allowed to reference local
|
||
variables, but this is somewhat arbitrary.</p>
|
||
<p>A final objection is that in a multi-threaded application, the
|
||
first-use rule requires intricate locking in order to guarantee the
|
||
correct semantics. (The first-use rule suggests a promise that side
|
||
effects of case expressions are incurred exactly once.) This may be
|
||
as tricky as the import lock has proved to be, since the lock has to
|
||
be held while all the case expressions are being evaluated.</p>
|
||
</section>
|
||
<section id="option-3">
|
||
<h4><a class="toc-backref" href="#option-3" role="doc-backlink">Option 3</a></h4>
|
||
<p>A proposal that has been winning support (including mine) is to freeze
|
||
a switch’s dict when the innermost function containing it is defined.
|
||
The switch dict is stored on the function object, just as parameter
|
||
defaults are, and in fact the case expressions are evaluated at the
|
||
same time and in the same scope as the parameter defaults (i.e. in the
|
||
scope containing the function definition).</p>
|
||
<p>This option has the advantage of avoiding many of the finesses needed
|
||
to make option 2 work: there’s no need for locking, no worry about
|
||
immutable code objects or multiple interpreters. It also provides a
|
||
clear explanation for why locals can’t be referenced in case
|
||
expressions.</p>
|
||
<p>This option works just as well for situations where one would
|
||
typically use a switch; case expressions involving imported or global
|
||
named constants work exactly the same way as in option 2, as long as
|
||
they are imported or defined before the function definition is
|
||
encountered.</p>
|
||
<p>A downside however is that the dispatch dict for a switch inside a
|
||
nested function must be recomputed each time the nested function is
|
||
defined. For certain “functional” styles of programming this may make
|
||
switch unattractive in nested functions. (Unless all case expressions
|
||
are compile-time constants; then the compiler is of course free to
|
||
optimize away the switch freezing code and make the dispatch table part
|
||
of the code object.)</p>
|
||
<p>Another downside is that under this option, there’s no clear moment
|
||
when the dispatch dict is frozen for a switch that doesn’t occur
|
||
inside a function. There are a few pragmatic choices for how to treat
|
||
a switch outside a function:</p>
|
||
<ol class="loweralpha simple">
|
||
<li>Disallow it.</li>
|
||
<li>Translate it into an if/elif chain.</li>
|
||
<li>Allow only compile-time constant expressions.</li>
|
||
<li>Compute the dispatch dict each time the switch is reached.</li>
|
||
<li>Like (b) but tests that all expressions evaluated are hashable.</li>
|
||
</ol>
|
||
<p>Of these, (a) seems too restrictive: it’s uniformly worse than (c);
|
||
and (d) has poor performance for little or no benefits compared to
|
||
(b). It doesn’t make sense to have a performance-critical inner loop
|
||
at the module level, as all local variable references are slow there;
|
||
hence (b) is my (weak) favorite. Perhaps I should favor (e), which
|
||
attempts to prevent atypical use of a switch; examples that work
|
||
interactively but not in a function are annoying. In the end I don’t
|
||
think this issue is all that important (except it must be resolved
|
||
somehow) and am willing to leave it up to whoever ends up implementing
|
||
it.</p>
|
||
<p>When a switch occurs in a class but not in a function, we can freeze
|
||
the dispatch dict at the same time the temporary function object
|
||
representing the class body is created. This means the case
|
||
expressions can reference module globals but not class variables.
|
||
Alternatively, if we choose (b) above, we could choose this
|
||
implementation inside a class definition as well.</p>
|
||
</section>
|
||
<section id="option-4">
|
||
<h4><a class="toc-backref" href="#option-4" role="doc-backlink">Option 4</a></h4>
|
||
<p>There are a number of proposals to add a construct to the language
|
||
that makes the concept of a value pre-computed at function definition
|
||
time generally available, without tying it either to parameter default
|
||
values or case expressions. Some keywords proposed include ‘const’,
|
||
‘static’, ‘only’ or ‘cached’. The associated syntax and semantics
|
||
vary.</p>
|
||
<p>These proposals are out of scope for this PEP, except to suggest that
|
||
<em>if</em> such a proposal is accepted, there are two ways for the switch to
|
||
benefit: we could require case expressions to be either compile-time
|
||
constants or pre-computed values; or we could make pre-computed values
|
||
the default (and only) evaluation mode for case expressions. The
|
||
latter would be my preference, since I don’t see a use for more
|
||
dynamic case expressions that isn’t addressed adequately by writing an
|
||
explicit if/elif chain.</p>
|
||
</section>
|
||
</section>
|
||
</section>
|
||
<section id="conclusion">
|
||
<h2><a class="toc-backref" href="#conclusion" role="doc-backlink">Conclusion</a></h2>
|
||
<p>It is too early to decide. I’d like to see at least one completed
|
||
proposal for pre-computed values before deciding. In the meantime,
|
||
Python is fine without a switch statement, and perhaps those who claim
|
||
it would be a mistake to add one are right.</p>
|
||
</section>
|
||
<section id="copyright">
|
||
<h2><a class="toc-backref" href="#copyright" role="doc-backlink">Copyright</a></h2>
|
||
<p>This document has been placed in the public domain.</p>
|
||
</section>
|
||
</section>
|
||
<hr class="docutils" />
|
||
<p>Source: <a class="reference external" href="https://github.com/python/peps/blob/main/peps/pep-3103.rst">https://github.com/python/peps/blob/main/peps/pep-3103.rst</a></p>
|
||
<p>Last modified: <a class="reference external" href="https://github.com/python/peps/commits/main/peps/pep-3103.rst">2023-09-09 17:39:29 GMT</a></p>
|
||
|
||
</article>
|
||
<nav id="pep-sidebar">
|
||
<h2>Contents</h2>
|
||
<ul>
|
||
<li><a class="reference internal" href="#rejection-notice">Rejection Notice</a></li>
|
||
<li><a class="reference internal" href="#abstract">Abstract</a></li>
|
||
<li><a class="reference internal" href="#rationale">Rationale</a></li>
|
||
<li><a class="reference internal" href="#basic-syntax">Basic Syntax</a><ul>
|
||
<li><a class="reference internal" href="#alternative-1">Alternative 1</a></li>
|
||
<li><a class="reference internal" href="#alternative-2">Alternative 2</a></li>
|
||
<li><a class="reference internal" href="#alternative-3">Alternative 3</a></li>
|
||
<li><a class="reference internal" href="#alternative-4">Alternative 4</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#extended-syntax">Extended Syntax</a><ul>
|
||
<li><a class="reference internal" href="#alternative-a">Alternative A</a></li>
|
||
<li><a class="reference internal" href="#alternative-b">Alternative B</a></li>
|
||
<li><a class="reference internal" href="#alternative-c">Alternative C</a></li>
|
||
<li><a class="reference internal" href="#alternative-d">Alternative D</a></li>
|
||
<li><a class="reference internal" href="#discussion">Discussion</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#semantics">Semantics</a><ul>
|
||
<li><a class="reference internal" href="#if-elif-chain-vs-dict-based-dispatch">If/Elif Chain vs. Dict-based Dispatch</a></li>
|
||
<li><a class="reference internal" href="#when-to-freeze-the-dispatch-dict">When to Freeze the Dispatch Dict</a><ul>
|
||
<li><a class="reference internal" href="#option-1">Option 1</a></li>
|
||
<li><a class="reference internal" href="#option-2">Option 2</a></li>
|
||
<li><a class="reference internal" href="#option-3">Option 3</a></li>
|
||
<li><a class="reference internal" href="#option-4">Option 4</a></li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#conclusion">Conclusion</a></li>
|
||
<li><a class="reference internal" href="#copyright">Copyright</a></li>
|
||
</ul>
|
||
|
||
<br>
|
||
<a id="source" href="https://github.com/python/peps/blob/main/peps/pep-3103.rst">Page Source (GitHub)</a>
|
||
</nav>
|
||
</section>
|
||
<script src="../_static/colour_scheme.js"></script>
|
||
<script src="../_static/wrap_tables.js"></script>
|
||
<script src="../_static/sticky_banner.js"></script>
|
||
</body>
|
||
</html> |