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:
parent
eda5cfd5d6
commit
4134c46262
186
pep-0653.rst
186
pep-0653.rst
|
@ -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]
|
||||
|
|
Loading…
Reference in New Issue