diff options
author | Daniel F Moisset <dfmoisset@gmail.com> | 2021-03-01 04:08:38 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-03-01 04:08:38 (GMT) |
commit | a22bca6b1e2f3dc11a58f3e5ef5c22e26b8a2d80 (patch) | |
tree | f2b840cbe218ee69d622697fa30b893069151b95 /Doc/tutorial | |
parent | d20279494a3a311c7aefa313174c45d32aa7f5d1 (diff) | |
download | cpython-a22bca6b1e2f3dc11a58f3e5ef5c22e26b8a2d80.zip cpython-a22bca6b1e2f3dc11a58f3e5ef5c22e26b8a2d80.tar.gz cpython-a22bca6b1e2f3dc11a58f3e5ef5c22e26b8a2d80.tar.bz2 |
bpo-42128: Add documentation for pattern matching (PEP 634) (#24664)
This is a first edition, ready to go out with the implementation. We'll iterate during the rest of the period leading up to 3.10.0.
Co-authored-by: Carol Willing <carolcode@willingconsulting.com>
Co-authored-by: Fidget-Spinner <28750310+Fidget-Spinner@users.noreply.github.com>
Co-authored-by: Brandt Bucher <brandt@python.org>
Co-authored-by: Raymond Hettinger <1623689+rhettinger@users.noreply.github.com>
Co-authored-by: Guido van Rossum <guido@python.org>
Diffstat (limited to 'Doc/tutorial')
-rw-r--r-- | Doc/tutorial/controlflow.rst | 169 |
1 files changed, 169 insertions, 0 deletions
diff --git a/Doc/tutorial/controlflow.rst b/Doc/tutorial/controlflow.rst index 9ee18f7..277e5c1 100644 --- a/Doc/tutorial/controlflow.rst +++ b/Doc/tutorial/controlflow.rst @@ -36,6 +36,9 @@ to avoid excessive indentation. An :keyword:`!if` ... :keyword:`!elif` ... :keyword:`!elif` ... sequence is a substitute for the ``switch`` or ``case`` statements found in other languages. +If you're comparing the same value to several constants, or checking for specific types or +attributes, you may also find the :keyword:`!match` statement useful. For more +details see :ref:`tut-match`. .. _tut-for: @@ -246,6 +249,172 @@ at a more abstract level. The :keyword:`!pass` is silently ignored:: ... pass # Remember to implement this! ... + +.. _tut-match: + +:keyword:`!match` Statements +============================ + +A match statement takes an expression and compares its value to successive +patterns given as one or more case blocks. This is superficially +similar to a switch statement in C, Java or JavaScript (and many +other languages), but it can also extract components (sequence elements or +object attributes) from the value into variables. + +The simplest form compares a subject value against one or more literals:: + + def http_error(status): + match status: + case 400: + return "Bad request" + case 404: + return "Not found" + case 418: + return "I'm a teapot" + case _: + return "Something's wrong with the Internet" + +Note the last block: the "variable name" ``_`` acts as a *wildcard* and +never fails to match. If no case matches, none of the branches is executed. + +You can combine several literals in a single pattern using ``|`` ("or"):: + + case 401 | 403 | 404: + return "Not allowed" + +Patterns can look like unpacking assignments, and can be used to bind +variables:: + + # point is an (x, y) tuple + match point: + case (0, 0): + print("Origin") + case (0, y): + print(f"Y={y}") + case (x, 0): + print(f"X={x}") + case (x, y): + print(f"X={x}, Y={y}") + case _: + raise ValueError("Not a point") + +Study that one carefully! The first pattern has two literals, and can +be thought of as an extension of the literal pattern shown above. But +the next two patterns combine a literal and a variable, and the +variable *binds* a value from the subject (``point``). The fourth +pattern captures two values, which makes it conceptually similar to +the unpacking assignment ``(x, y) = point``. + +If you are using classes to structure your data +you can use the class name followed by an argument list resembling a +constructor, but with the ability to capture attributes into variables:: + + class Point: + x: int + y: int + + def where_is(point): + match point: + case Point(x=0, y=0): + print("Origin") + case Point(x=0, y=y): + print(f"Y={y}") + case Point(x=x, y=0): + print(f"X={x}") + case Point(): + print("Somewhere else") + case _: + print("Not a point") + +You can use positional parameters with some builtin classes that provide an +ordering for their attributes (e.g. dataclasses). You can also define a specific +position for attributes in patterns by setting the ``__match_args__`` special +attribute in your classes. If it's set to ("x", "y"), the following patterns are all +equivalent (and all bind the ``y`` attribute to the ``var`` variable):: + + Point(1, var) + Point(1, y=var) + Point(x=1, y=var) + Point(y=var, x=1) + +A recommended way to read patterns is to look at them as an extended form of what you +would put on the left of an assignment, to understand which variables would be set to +what. +Only the standalone names (like ``var`` above) are assigned to by a match statement. +Dotted names (like ``foo.bar``), attribute names (the ``x=`` and ``y=`` above) or class names +(recognized by the "(...)" next to them like ``Point`` above) are never assigned to. + +Patterns can be arbitrarily nested. For example, if we have a short +list of points, we could match it like this:: + + match points: + case []: + print("No points") + case [Point(0, 0)]: + print("The origin") + case [Point(x, y)]: + print(f"Single point {x}, {y}") + case [Point(0, y1), Point(0, y2)]: + print(f"Two on the Y axis at {y1}, {y2}") + case _: + print("Something else") + +We can add an ``if`` clause to a pattern, known as a "guard". If the +guard is false, ``match`` goes on to try the next case block. Note +that value capture happens before the guard is evaluated:: + + match point: + case Point(x, y) if x == y: + print(f"Y=X at {x}") + case Point(x, y): + print(f"Not on the diagonal") + +Several other key features of this statement: + +- Like unpacking assignments, tuple and list patterns have exactly the + same meaning and actually match arbitrary sequences. An important + exception is that they don't match iterators or strings. + +- Sequence patterns support extended unpacking: ``[x, y, *rest]`` and ``(x, y, + *rest)`` work similar to unpacking assignments. The + name after ``*`` may also be ``_``, so ``(x, y, *_)`` matches a sequence + of at least two items without binding the remaining items. + +- Mapping patterns: ``{"bandwidth": b, "latency": l}`` captures the + ``"bandwidth"`` and ``"latency"`` values from a dictionary. Unlike sequence + patterns, extra keys are ignored. An unpacking like ``**rest`` is also + supported. (But ``**_`` would be redundant, so it not allowed.) + +- Subpatterns may be captured using the ``as`` keyword:: + + case (Point(x1, y1), Point(x2, y2) as p2): ... + + will capture the second element of the input as ``p2`` (as long as the input is + a sequence of two points) + +- Most literals are compared by equality, however the singletons ``True``, + ``False`` and ``None`` are compared by identity. + +- Patterns may use named constants. These must be dotted names + to prevent them from being interpreted as capture variable:: + + from enum import Enum + class Color(Enum): + RED = 0 + GREEN = 1 + BLUE = 2 + + match color: + case Color.RED: + print("I see red!") + case Color.GREEN: + print("Grass is green") + case Color.BLUE: + print("I'm feeling the blues :(") + +For a more detailed explanation and additional examples, you can look into +:pep:`636` which is written in a tutorial format. + .. _tut-functions: Defining Functions |