Add PEP 3103: A Switch/Case Statement (GvR).
Also fix a reference to cvs in the Makefile.
This commit is contained in:
parent
493d7e9d7d
commit
3e857976b3
|
@ -99,6 +99,7 @@ Index by Category
|
|||
S 754 IEEE 754 Floating Point Special Values Warnes
|
||||
S 3101 Advanced String Formatting Talin
|
||||
S 3102 Keyword-Only Arguments Talin
|
||||
S 3103 A Switch/Case Statement GvR
|
||||
|
||||
Finished PEPs (done, implemented in Subversion)
|
||||
|
||||
|
@ -431,6 +432,7 @@ Numerical Index
|
|||
I 3100 Python 3.0 Plans Kuchling, Cannon
|
||||
S 3101 Advanced String Formatting Talin
|
||||
S 3102 Keyword-Only Arguments Talin
|
||||
S 3103 A Switch/Case Statement GvR
|
||||
|
||||
Key
|
||||
|
||||
|
|
126
pep-3103.txt
126
pep-3103.txt
|
@ -8,7 +8,7 @@ Type: Standards Track
|
|||
Python-Version: 3.0
|
||||
Content-Type: text/x-rst
|
||||
Created: 25-Jun-2006
|
||||
Post-History: never
|
||||
Post-History: 26-Jun-2006
|
||||
|
||||
|
||||
Abstract
|
||||
|
@ -24,6 +24,11 @@ This PEP should be seen as an alternative to PEP 275. My views are
|
|||
somewhat different from that PEP's author, but I'm grateful for the
|
||||
work done in that PEP.
|
||||
|
||||
This PEP introduces canonical names for the many variants that have
|
||||
been discussed for different aspects of the syntax and semantics, such
|
||||
as "alternative 2", "school II", "Option 3" and so on. Hopefully
|
||||
these names will help the discussion.
|
||||
|
||||
|
||||
Rationale
|
||||
=========
|
||||
|
@ -216,10 +221,10 @@ like this::
|
|||
|
||||
case [*]EXPR, [*]EXPR, ...:
|
||||
|
||||
Note that the * notation is similar to the use of prefix * already in
|
||||
use for variable-length parameter lists and for passing computed
|
||||
argument lists, and often proposed for value-unpacking (e.g. "a, b,
|
||||
*c = X" as an alternative to "(a, b), c = X[:2], X[2:]").
|
||||
The * notation is similar to the use of prefix * already in use for
|
||||
variable-length parameter lists and for passing computed argument
|
||||
lists, and often proposed for value-unpacking (e.g. "a, b, *c = X" as
|
||||
an alternative to "(a, b), c = X[:2], X[2:]").
|
||||
|
||||
Alternative D
|
||||
-------------
|
||||
|
@ -390,16 +395,17 @@ 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.
|
||||
|
||||
A mostly theoretical 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.
|
||||
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.
|
||||
|
||||
Another objection is that the first-use rule allows obfuscated code
|
||||
like this::
|
||||
|
@ -419,17 +425,101 @@ equivalent to this::
|
|||
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.
|
||||
variables, but this is somewhat arbitrary.
|
||||
|
||||
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.)
|
||||
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.
|
||||
|
||||
Option 3
|
||||
''''''''
|
||||
|
||||
TBD
|
||||
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).
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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 swich freezing code and make the dispatch table part
|
||||
of the code object.)
|
||||
|
||||
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:
|
||||
|
||||
(a) Disallow it.
|
||||
(b) Translate it into an if/elif chain.
|
||||
(c) Allow only compile-time constant expressions.
|
||||
(d) Compute the dispatch dict each time the switch is reached.
|
||||
(e) Like (b) but tests that all expressions evaluated are hashable.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
Option 4
|
||||
''''''''
|
||||
|
||||
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.
|
||||
|
||||
These proposals are out of scope for this PEP, except to suggest that
|
||||
*if* 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.
|
||||
|
||||
|
||||
Conclusion
|
||||
==========
|
||||
|
||||
It is too early to decide. I'd like to see at least one completed
|
||||
proposal for pre-computed values before deciding. In the mean time,
|
||||
Python is fine without a switch statement, and perhaps those who claim
|
||||
it would be a mistake to add one are right.
|
||||
|
||||
|
||||
Copyright
|
||||
|
|
Loading…
Reference in New Issue