summaryrefslogtreecommitdiffstats
path: root/Doc/tutorial
diff options
context:
space:
mode:
authorDaniel F Moisset <dfmoisset@gmail.com>2021-03-01 04:08:38 (GMT)
committerGitHub <noreply@github.com>2021-03-01 04:08:38 (GMT)
commita22bca6b1e2f3dc11a58f3e5ef5c22e26b8a2d80 (patch)
treef2b840cbe218ee69d622697fa30b893069151b95 /Doc/tutorial
parentd20279494a3a311c7aefa313174c45d32aa7f5d1 (diff)
downloadcpython-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.rst169
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