Simplifications of PEP 653: (#1919)

* Make 0 the defualt for __match_class__ and drop ability to not match class patterns

* Use equality not bitwise ands to check for matches

* Add to rejected and deferred sections, explaining the above
This commit is contained in:
Mark Shannon 2021-04-12 12:17:50 +01:00 committed by GitHub
parent eda5cfd5d6
commit 4134c46262
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 83 additions and 103 deletions

View File

@ -16,7 +16,7 @@ but is more precise, easier to reason about, and should be faster.
The object model will be extended with two special (dunder) attributes, ``__match_container__`` and
``__match_class__``, in addition to the ``__match_args__`` attribute from PEP 634, to support pattern matching.
Both of these new attributes must be integers and ``__match_args__`` is required to be a tuple.
Both of these new attributes must be integers and ``__match_args__`` is required to be a tuple of unique strings.
With this PEP:
@ -114,29 +114,34 @@ The ``__match_container__ ``and ``__match_class__`` attributes will be added to
``__match_container__`` must be an integer and should be exactly one of these::
0
MATCH_SEQUENCE
MATCH_MAPPING
MATCH_SEQUENCE = 1
MATCH_MAPPING = 2
``MATCH_SEQUENCE`` is used to indicate that instances of the class can match sequence patterns.
``MATCH_MAPPING`` is used to indicate that instances of the class can match mapping patterns.
``__match_class__`` must be an integer and should be exactly one of these::
0
MATCH_ATTRIBUTES
MATCH_SELF
MATCH_SELF = 8
``MATCH_SELF`` is used to indicate that for a single positional argument class pattern, the subject will be used and not deconstructed.
.. note::
It does not matter what the actual values are. We will refer to them by name only.
Symbolic constants will be provided both for Python and C, and once defined they will
In the rest of this document, we will refer to the above values by name only.
Symbolic constants will be provided both for Python and C, and the values will
never be changed.
``object`` will have the following values for the special attributes::
__match_container__ = 0
__match_class__= MATCH_ATTRIBUTES
__match_class__= 0
__match_args__ = ()
These special attributes will be inherited as normal.
If ``__match_args__`` is overridden, then it is required to hold a tuple of strings. It may be empty.
If ``__match_args__`` is overridden, then it is required to hold a tuple of unique strings. It may be empty.
.. note::
``__match_args__`` will be automatically generated for dataclasses and named tuples, as specified in PEP 634.
@ -153,7 +158,6 @@ In the following, all variables of the form ``$var`` are temporary variables and
They may be visible via introspection, but that is an implementation detail and should not be relied on.
The psuedo-statement ``FAIL`` is used to signify that matching failed for this pattern and that matching should move to the next pattern.
If control reaches the end of the translation without reaching a ``FAIL``, then it has matched, and following patterns are ignored.
All the translations below include guards. If no guard is present, simply substitute the guard ``if True`` when translating.
Variables of the form ``$ALL_CAPS`` are meta-variables holding a syntactic element, they are not normal variables.
So, ``$VARS = $items`` is not an assignment of ``$items`` to ``$VARS``,
@ -241,7 +245,7 @@ A pattern not including a star pattern::
translates to::
$kind = type($value).__match_container__
if $kind & MATCH_SEQUENCE == 0:
if $kind != MATCH_SEQUENCE:
FAIL
if len($value) != len($VARS):
FAIL
@ -256,7 +260,7 @@ A pattern including a star pattern::
translates to::
$kind = type($value).__match_container__
if $kind & MATCH_SEQUENCE == 0:
if $kind != MATCH_SEQUENCE:
FAIL
if len($value) < len($VARS):
FAIL
@ -275,7 +279,7 @@ translates to::
$sentinel = object()
$kind = type($value).__match_container__
if $kind & MATCH_MAPPING == 0:
if $kind != MATCH_MAPPING:
FAIL
# $KEYWORD_PATTERNS is a meta-variable mapping names to variables.
for $KEYWORD in $KEYWORD_PATTERNS:
@ -293,7 +297,7 @@ A pattern including a double-star pattern::
translates to::
$kind = type($value).__match_container__
if $kind & MATCH_MAPPING == 0:
if $kind != MATCH_MAPPING:
FAIL
# $KEYWORD_PATTERNS is a meta-variable mapping names to variables.
$tmp = dict($value)
@ -317,12 +321,6 @@ translates to::
if not isinstance($value, ClsName):
FAIL
.. note::
``case ClsName():`` is the only class pattern that can succeed if
``($kind & (MATCH_SELF|MATCH_ATTRIBUTES)) == 0``
Class pattern with a single positional pattern::
case ClsName($VAR):
@ -330,7 +328,7 @@ Class pattern with a single positional pattern::
translates to::
$kind = type($value).__match_class__
if $kind & MATCH_SELF:
if $kind == MATCH_SELF:
if not isinstance($value, ClsName):
FAIL
$VAR = $value
@ -346,17 +344,13 @@ translates to::
if not isinstance($value, ClsName):
FAIL
$kind = type($value).__match_class__
if $kind & MATCH_ATTRIBUTES:
$attrs = ClsName.__match_args__
if len($attr) < len($VARS):
raise TypeError(...)
try:
for i, $VAR in enumerate($VARS):
$VAR = getattr($value, $attrs[i])
except AttributeError:
FAIL
else:
$attrs = ClsName.__match_args__
if len($attr) < len($VARS):
raise TypeError(...)
try:
for i, $VAR in enumerate($VARS):
$VAR = getattr($value, $attrs[i])
except AttributeError:
FAIL
Example: [6]_
@ -369,15 +363,11 @@ translates to::
if not isinstance($value, ClsName):
FAIL
$kind = type($value).__match_class__
if $kind & MATCH_ATTRIBUTES:
try:
for $KEYWORD in $KEYWORD_PATTERNS:
$tmp = getattr($value, QUOTE($KEYWORD))
$KEYWORD_PATTERNS[$KEYWORD] = $tmp
except AttributeError:
FAIL
else:
try:
for $KEYWORD in $KEYWORD_PATTERNS:
$tmp = getattr($value, QUOTE($KEYWORD))
$KEYWORD_PATTERNS[$KEYWORD] = $tmp
except AttributeError:
FAIL
Example: [7]_
@ -390,23 +380,19 @@ translates to::
if not isinstance($value, ClsName):
FAIL
$kind = type($value).__match_class__
if $kind & MATCH_ATTRIBUTES:
$attrs = ClsName.__match_args__
if len($attr) < len($VARS):
raise TypeError(...)
$pos_attrs = $attrs[:len($VARS)]
try:
for i, $VAR in enumerate($VARS):
$VAR = getattr($value, $attrs[i])
for $KEYWORD in $KEYWORD_PATTERNS:
$name = QUOTE($KEYWORD)
if $name in pos_attrs:
raise TypeError(...)
$KEYWORD_PATTERNS[$KEYWORD] = getattr($value, $name)
except AttributeError:
FAIL
else:
$attrs = ClsName.__match_args__
if len($attr) < len($VARS):
raise TypeError(...)
$pos_attrs = $attrs[:len($VARS)]
try:
for i, $VAR in enumerate($VARS):
$VAR = getattr($value, $attrs[i])
for $KEYWORD in $KEYWORD_PATTERNS:
$name = QUOTE($KEYWORD)
if $name in pos_attrs:
raise TypeError(...)
$KEYWORD_PATTERNS[$KEYWORD] = getattr($value, $name)
except AttributeError:
FAIL
Example: [8]_
@ -425,7 +411,7 @@ For example, the pattern::
translates to::
$kind = type($value).__match_class__
if $kind & MATCH_SEQUENCE == 0:
if $kind != MATCH_SEQUENCE:
FAIL
if len($value) != 2:
FAIL
@ -457,11 +443,10 @@ All classes should ensure that the the values of ``__match_container__``, ``__ma
and ``__match_args__`` follow the specification.
Therefore, implementations can assume, without checking, that the following are true::
(__match_container__ & (MATCH_SEQUENCE | MATCH_MAPPING)) != (MATCH_SEQUENCE | MATCH_MAPPING)
(__match_class__ & (MATCH_SELF | MATCH_ATTRIBUTES)) != (MATCH_SELF | MATCH_ATTRIBUTES)
__match_container__ == 0 or __match_container__ == MATCH_SEQUENCE or __match_container__ == MATCH_MAPPING
__match_class__ == 0 or __match_class__ == MATCH_SELF
Thus, implementations can assume that ``__match_container__ & MATCH_SEQUENCE`` implies ``(__match_container__ & MATCH_MAPPING) == 0``, and vice-versa.
Likewise for ``__match_class__``, ``MATCH_SELF`` and ``MATCH_ATTRIBUTES``.
and that ``__match_args__`` is a tuple of unique strings.
Values of the special attributes for classes in the standard library
--------------------------------------------------------------------
@ -518,9 +503,9 @@ Implementations are allowed to make the following assumptions:
* ``isinstance(obj, cls)`` can be freely replaced with ``issubclass(type(obj), cls)`` and vice-versa.
* ``isinstance(obj, cls)`` will always return the same result for any ``(obj, cls)`` pair and repeated calls can thus be elided.
* Reading any of ``__match_container__``, ``__match_class__`` or ``__match_args__`` is a pure operation, and may be cached.
* Sequences, that is any class for which ``__match_container__&MATCH_SEQUENCE`` is not zero, are not modified by iteration, subscripting or calls to ``len()``.
* Sequences, that is any class for which ``__match_container__ == MATCH_SEQUENCE`` is not zero, are not modified by iteration, subscripting or calls to ``len()``.
Consequently, those operations can be freely substituted for each other where they would be equivalent when applied to an immutable sequence.
* Mappings, that is any class for which ``__match_container__&MATCH_MAPPING`` is not zero, will not capture the second argument of the ``get()`` method.
* Mappings, that is any class for which ``__match_container__ == MATCH_MAPPING`` is not zero, will not capture the second argument of the ``get()`` method.
So, the ``$sentinel`` value may be freely re-used.
In fact, implementations are encouraged to make these assumptions, as it is likely to result in signficantly better performance.
@ -668,7 +653,7 @@ Rejected Ideas
Using attributes from the instance's dictionary
-----------------------------------------------
An earlier version of this PEP only used attributes from the instance's dictionary when matching a class pattern with ``MATCH_ATTRIBUTES``.
An earlier version of this PEP only used attributes from the instance's dictionary when matching a class pattern with ``__match_class__`` was the default value.
The intent was to avoid capturing bound-methods and other synthetic attributes. However, this also mean that properties were ignored.
For the class::
@ -683,7 +668,7 @@ For the class::
...
Ideally we would match the attributes "a" and "p", but not "m".
However, there is no general way to do that, so this PEP now follows the semantics of PEP 634 for ``MATCH_ATTRIBUTES``.
However, there is no general way to do that, so this PEP now follows the semantics of PEP 634.
Lookup of ``__match_args__`` on the subject not the pattern
-----------------------------------------------------------
@ -703,6 +688,7 @@ An earlier version of this PEP combined ``__match_class__`` and ``__match_contai
Using a single value has a small advantage in terms of performance,
but is likely to result in unintended changes to container matching when overriding class matching behavior, and vice versa.
Deferred Ideas
==============
@ -722,6 +708,12 @@ but is tricky. With these additional features it can be implemented easily [9]_.
This idea will feature in a future PEP for 3.11.
However, it is too late in the 3.10 development cycle for such a change.
Having a separate value to reject all class matches
---------------------------------------------------
In an earlier version of this PEP, there was a distinct value for ``__match_class__`` that allowed classes to not match any class
pattern that would have required deconstruction. However, this would become redundant once ``MATCH_POSITIONAL`` is introduced, and
complicates the specification for an extremely rare case.
References
==========
@ -748,7 +740,7 @@ This::
translates to::
$kind = type($value).__match_container__
if $kind & MATCH_SEQUENCE == 0:
if $kind != MATCH_SEQUENCE:
FAIL
if len($value) != 2:
FAIL
@ -765,7 +757,7 @@ This::
translates to::
$kind = type($value).__match_container__
if $kind & MATCH_SEQUENCE == 0:
if $kind != MATCH_SEQUENCE:
FAIL
if len($value) < 2:
FAIL
@ -780,7 +772,7 @@ This::
translates to::
$kind = type($value).__match_container__
if $kind & MATCH_MAPPING == 0:
if $kind != MATCH_MAPPING:
FAIL
$tmp = $value.get("x", $sentinel)
if $tmp is $sentinel:
@ -802,7 +794,7 @@ This::
translates to::
$kind = type($value).__match_container__
if $kind & MATCH_MAPPING == 0:
if $kind != MATCH_MAPPING:
FAIL
$tmp = dict($value)
if not $tmp.keys() >= {"x", "y"}:
@ -821,17 +813,13 @@ translates to::
if not isinstance($value, ClsName):
FAIL
$kind = type($value).__match_class__
if $kind & MATCH_ATTRIBUTES:
$attrs = ClsName.__match_args__
if len($attr) < 2:
FAIL
try:
x = getattr($value, $attrs[0])
y = getattr($value, $attrs[1])
except AttributeError:
FAIL
else:
$attrs = ClsName.__match_args__
if len($attr) < 2:
FAIL
try:
x = getattr($value, $attrs[0])
y = getattr($value, $attrs[1])
except AttributeError:
FAIL
.. [7]
@ -844,14 +832,10 @@ translates to::
if not isinstance($value, ClsName):
FAIL
$kind = type($value).__match_class__
lif $kind & MATCH_ATTRIBUTES:
try:
x = $value.a
y = $value.b
except AttributeError:
FAIL
else:
try:
x = $value.a
y = $value.b
except AttributeError:
FAIL
.. [8]
@ -865,20 +849,16 @@ translates to::
if not isinstance($value, ClsName):
FAIL
$kind = type($value).__match_class__
if $kind & MATCH_ATTRIBUTES:
$attrs = ClsName.__match_args__
if len($attr) < 1:
$attrs = ClsName.__match_args__
if len($attr) < 1:
raise TypeError(...)
$positional_names = $attrs[:1]
try:
x = getattr($value, $attrs[0])
if "a" in $positional_names:
raise TypeError(...)
$positional_names = $attrs[:1]
try:
x = getattr($value, $attrs[0])
if "a" in $positional_names:
raise TypeError(...)
y = $value.a
except AttributeError:
FAIL
else:
y = $value.a
except AttributeError:
FAIL
.. [9]