mirror of
https://github.com/python/cpython.git
synced 2025-11-02 03:01:58 +00:00
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>
This commit is contained in:
parent
d20279494a
commit
a22bca6b1e
5 changed files with 821 additions and 3 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue