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
|
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.
|
``__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:
|
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::
|
``__match_container__`` must be an integer and should be exactly one of these::
|
||||||
|
|
||||||
0
|
0
|
||||||
MATCH_SEQUENCE
|
MATCH_SEQUENCE = 1
|
||||||
MATCH_MAPPING
|
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::
|
``__match_class__`` must be an integer and should be exactly one of these::
|
||||||
|
|
||||||
0
|
0
|
||||||
MATCH_ATTRIBUTES
|
MATCH_SELF = 8
|
||||||
MATCH_SELF
|
|
||||||
|
``MATCH_SELF`` is used to indicate that for a single positional argument class pattern, the subject will be used and not deconstructed.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
It does not matter what the actual values are. We will refer to them by name only.
|
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 once defined they will
|
Symbolic constants will be provided both for Python and C, and the values will
|
||||||
never be changed.
|
never be changed.
|
||||||
|
|
||||||
``object`` will have the following values for the special attributes::
|
``object`` will have the following values for the special attributes::
|
||||||
|
|
||||||
__match_container__ = 0
|
__match_container__ = 0
|
||||||
__match_class__= MATCH_ATTRIBUTES
|
__match_class__= 0
|
||||||
__match_args__ = ()
|
__match_args__ = ()
|
||||||
|
|
||||||
These special attributes will be inherited as normal.
|
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::
|
.. note::
|
||||||
``__match_args__`` will be automatically generated for dataclasses and named tuples, as specified in PEP 634.
|
``__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.
|
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.
|
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.
|
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.
|
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``,
|
So, ``$VARS = $items`` is not an assignment of ``$items`` to ``$VARS``,
|
||||||
|
@ -241,7 +245,7 @@ A pattern not including a star pattern::
|
||||||
translates to::
|
translates to::
|
||||||
|
|
||||||
$kind = type($value).__match_container__
|
$kind = type($value).__match_container__
|
||||||
if $kind & MATCH_SEQUENCE == 0:
|
if $kind != MATCH_SEQUENCE:
|
||||||
FAIL
|
FAIL
|
||||||
if len($value) != len($VARS):
|
if len($value) != len($VARS):
|
||||||
FAIL
|
FAIL
|
||||||
|
@ -256,7 +260,7 @@ A pattern including a star pattern::
|
||||||
translates to::
|
translates to::
|
||||||
|
|
||||||
$kind = type($value).__match_container__
|
$kind = type($value).__match_container__
|
||||||
if $kind & MATCH_SEQUENCE == 0:
|
if $kind != MATCH_SEQUENCE:
|
||||||
FAIL
|
FAIL
|
||||||
if len($value) < len($VARS):
|
if len($value) < len($VARS):
|
||||||
FAIL
|
FAIL
|
||||||
|
@ -275,7 +279,7 @@ translates to::
|
||||||
|
|
||||||
$sentinel = object()
|
$sentinel = object()
|
||||||
$kind = type($value).__match_container__
|
$kind = type($value).__match_container__
|
||||||
if $kind & MATCH_MAPPING == 0:
|
if $kind != MATCH_MAPPING:
|
||||||
FAIL
|
FAIL
|
||||||
# $KEYWORD_PATTERNS is a meta-variable mapping names to variables.
|
# $KEYWORD_PATTERNS is a meta-variable mapping names to variables.
|
||||||
for $KEYWORD in $KEYWORD_PATTERNS:
|
for $KEYWORD in $KEYWORD_PATTERNS:
|
||||||
|
@ -293,7 +297,7 @@ A pattern including a double-star pattern::
|
||||||
translates to::
|
translates to::
|
||||||
|
|
||||||
$kind = type($value).__match_container__
|
$kind = type($value).__match_container__
|
||||||
if $kind & MATCH_MAPPING == 0:
|
if $kind != MATCH_MAPPING:
|
||||||
FAIL
|
FAIL
|
||||||
# $KEYWORD_PATTERNS is a meta-variable mapping names to variables.
|
# $KEYWORD_PATTERNS is a meta-variable mapping names to variables.
|
||||||
$tmp = dict($value)
|
$tmp = dict($value)
|
||||||
|
@ -317,12 +321,6 @@ translates to::
|
||||||
if not isinstance($value, ClsName):
|
if not isinstance($value, ClsName):
|
||||||
FAIL
|
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::
|
Class pattern with a single positional pattern::
|
||||||
|
|
||||||
case ClsName($VAR):
|
case ClsName($VAR):
|
||||||
|
@ -330,7 +328,7 @@ Class pattern with a single positional pattern::
|
||||||
translates to::
|
translates to::
|
||||||
|
|
||||||
$kind = type($value).__match_class__
|
$kind = type($value).__match_class__
|
||||||
if $kind & MATCH_SELF:
|
if $kind == MATCH_SELF:
|
||||||
if not isinstance($value, ClsName):
|
if not isinstance($value, ClsName):
|
||||||
FAIL
|
FAIL
|
||||||
$VAR = $value
|
$VAR = $value
|
||||||
|
@ -346,17 +344,13 @@ translates to::
|
||||||
|
|
||||||
if not isinstance($value, ClsName):
|
if not isinstance($value, ClsName):
|
||||||
FAIL
|
FAIL
|
||||||
$kind = type($value).__match_class__
|
$attrs = ClsName.__match_args__
|
||||||
if $kind & MATCH_ATTRIBUTES:
|
if len($attr) < len($VARS):
|
||||||
$attrs = ClsName.__match_args__
|
raise TypeError(...)
|
||||||
if len($attr) < len($VARS):
|
try:
|
||||||
raise TypeError(...)
|
for i, $VAR in enumerate($VARS):
|
||||||
try:
|
$VAR = getattr($value, $attrs[i])
|
||||||
for i, $VAR in enumerate($VARS):
|
except AttributeError:
|
||||||
$VAR = getattr($value, $attrs[i])
|
|
||||||
except AttributeError:
|
|
||||||
FAIL
|
|
||||||
else:
|
|
||||||
FAIL
|
FAIL
|
||||||
|
|
||||||
Example: [6]_
|
Example: [6]_
|
||||||
|
@ -369,15 +363,11 @@ translates to::
|
||||||
|
|
||||||
if not isinstance($value, ClsName):
|
if not isinstance($value, ClsName):
|
||||||
FAIL
|
FAIL
|
||||||
$kind = type($value).__match_class__
|
try:
|
||||||
if $kind & MATCH_ATTRIBUTES:
|
for $KEYWORD in $KEYWORD_PATTERNS:
|
||||||
try:
|
$tmp = getattr($value, QUOTE($KEYWORD))
|
||||||
for $KEYWORD in $KEYWORD_PATTERNS:
|
$KEYWORD_PATTERNS[$KEYWORD] = $tmp
|
||||||
$tmp = getattr($value, QUOTE($KEYWORD))
|
except AttributeError:
|
||||||
$KEYWORD_PATTERNS[$KEYWORD] = $tmp
|
|
||||||
except AttributeError:
|
|
||||||
FAIL
|
|
||||||
else:
|
|
||||||
FAIL
|
FAIL
|
||||||
|
|
||||||
Example: [7]_
|
Example: [7]_
|
||||||
|
@ -390,23 +380,19 @@ translates to::
|
||||||
|
|
||||||
if not isinstance($value, ClsName):
|
if not isinstance($value, ClsName):
|
||||||
FAIL
|
FAIL
|
||||||
$kind = type($value).__match_class__
|
$attrs = ClsName.__match_args__
|
||||||
if $kind & MATCH_ATTRIBUTES:
|
if len($attr) < len($VARS):
|
||||||
$attrs = ClsName.__match_args__
|
raise TypeError(...)
|
||||||
if len($attr) < len($VARS):
|
$pos_attrs = $attrs[:len($VARS)]
|
||||||
raise TypeError(...)
|
try:
|
||||||
$pos_attrs = $attrs[:len($VARS)]
|
for i, $VAR in enumerate($VARS):
|
||||||
try:
|
$VAR = getattr($value, $attrs[i])
|
||||||
for i, $VAR in enumerate($VARS):
|
for $KEYWORD in $KEYWORD_PATTERNS:
|
||||||
$VAR = getattr($value, $attrs[i])
|
$name = QUOTE($KEYWORD)
|
||||||
for $KEYWORD in $KEYWORD_PATTERNS:
|
if $name in pos_attrs:
|
||||||
$name = QUOTE($KEYWORD)
|
raise TypeError(...)
|
||||||
if $name in pos_attrs:
|
$KEYWORD_PATTERNS[$KEYWORD] = getattr($value, $name)
|
||||||
raise TypeError(...)
|
except AttributeError:
|
||||||
$KEYWORD_PATTERNS[$KEYWORD] = getattr($value, $name)
|
|
||||||
except AttributeError:
|
|
||||||
FAIL
|
|
||||||
else:
|
|
||||||
FAIL
|
FAIL
|
||||||
|
|
||||||
Example: [8]_
|
Example: [8]_
|
||||||
|
@ -425,7 +411,7 @@ For example, the pattern::
|
||||||
translates to::
|
translates to::
|
||||||
|
|
||||||
$kind = type($value).__match_class__
|
$kind = type($value).__match_class__
|
||||||
if $kind & MATCH_SEQUENCE == 0:
|
if $kind != MATCH_SEQUENCE:
|
||||||
FAIL
|
FAIL
|
||||||
if len($value) != 2:
|
if len($value) != 2:
|
||||||
FAIL
|
FAIL
|
||||||
|
@ -457,11 +443,10 @@ All classes should ensure that the the values of ``__match_container__``, ``__ma
|
||||||
and ``__match_args__`` follow the specification.
|
and ``__match_args__`` follow the specification.
|
||||||
Therefore, implementations can assume, without checking, that the following are true::
|
Therefore, implementations can assume, without checking, that the following are true::
|
||||||
|
|
||||||
(__match_container__ & (MATCH_SEQUENCE | MATCH_MAPPING)) != (MATCH_SEQUENCE | MATCH_MAPPING)
|
__match_container__ == 0 or __match_container__ == MATCH_SEQUENCE or __match_container__ == MATCH_MAPPING
|
||||||
(__match_class__ & (MATCH_SELF | MATCH_ATTRIBUTES)) != (MATCH_SELF | MATCH_ATTRIBUTES)
|
__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.
|
and that ``__match_args__`` is a tuple of unique strings.
|
||||||
Likewise for ``__match_class__``, ``MATCH_SELF`` and ``MATCH_ATTRIBUTES``.
|
|
||||||
|
|
||||||
Values of the special attributes for classes in the standard library
|
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)`` 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.
|
* ``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.
|
* 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.
|
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.
|
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.
|
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
|
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.
|
The intent was to avoid capturing bound-methods and other synthetic attributes. However, this also mean that properties were ignored.
|
||||||
|
|
||||||
For the class::
|
For the class::
|
||||||
|
@ -683,7 +668,7 @@ For the class::
|
||||||
...
|
...
|
||||||
|
|
||||||
Ideally we would match the attributes "a" and "p", but not "m".
|
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
|
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,
|
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.
|
but is likely to result in unintended changes to container matching when overriding class matching behavior, and vice versa.
|
||||||
|
|
||||||
|
|
||||||
Deferred Ideas
|
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.
|
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.
|
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
|
References
|
||||||
==========
|
==========
|
||||||
|
@ -748,7 +740,7 @@ This::
|
||||||
translates to::
|
translates to::
|
||||||
|
|
||||||
$kind = type($value).__match_container__
|
$kind = type($value).__match_container__
|
||||||
if $kind & MATCH_SEQUENCE == 0:
|
if $kind != MATCH_SEQUENCE:
|
||||||
FAIL
|
FAIL
|
||||||
if len($value) != 2:
|
if len($value) != 2:
|
||||||
FAIL
|
FAIL
|
||||||
|
@ -765,7 +757,7 @@ This::
|
||||||
translates to::
|
translates to::
|
||||||
|
|
||||||
$kind = type($value).__match_container__
|
$kind = type($value).__match_container__
|
||||||
if $kind & MATCH_SEQUENCE == 0:
|
if $kind != MATCH_SEQUENCE:
|
||||||
FAIL
|
FAIL
|
||||||
if len($value) < 2:
|
if len($value) < 2:
|
||||||
FAIL
|
FAIL
|
||||||
|
@ -780,7 +772,7 @@ This::
|
||||||
translates to::
|
translates to::
|
||||||
|
|
||||||
$kind = type($value).__match_container__
|
$kind = type($value).__match_container__
|
||||||
if $kind & MATCH_MAPPING == 0:
|
if $kind != MATCH_MAPPING:
|
||||||
FAIL
|
FAIL
|
||||||
$tmp = $value.get("x", $sentinel)
|
$tmp = $value.get("x", $sentinel)
|
||||||
if $tmp is $sentinel:
|
if $tmp is $sentinel:
|
||||||
|
@ -802,7 +794,7 @@ This::
|
||||||
translates to::
|
translates to::
|
||||||
|
|
||||||
$kind = type($value).__match_container__
|
$kind = type($value).__match_container__
|
||||||
if $kind & MATCH_MAPPING == 0:
|
if $kind != MATCH_MAPPING:
|
||||||
FAIL
|
FAIL
|
||||||
$tmp = dict($value)
|
$tmp = dict($value)
|
||||||
if not $tmp.keys() >= {"x", "y"}:
|
if not $tmp.keys() >= {"x", "y"}:
|
||||||
|
@ -821,17 +813,13 @@ translates to::
|
||||||
|
|
||||||
if not isinstance($value, ClsName):
|
if not isinstance($value, ClsName):
|
||||||
FAIL
|
FAIL
|
||||||
$kind = type($value).__match_class__
|
$attrs = ClsName.__match_args__
|
||||||
if $kind & MATCH_ATTRIBUTES:
|
if len($attr) < 2:
|
||||||
$attrs = ClsName.__match_args__
|
FAIL
|
||||||
if len($attr) < 2:
|
try:
|
||||||
FAIL
|
x = getattr($value, $attrs[0])
|
||||||
try:
|
y = getattr($value, $attrs[1])
|
||||||
x = getattr($value, $attrs[0])
|
except AttributeError:
|
||||||
y = getattr($value, $attrs[1])
|
|
||||||
except AttributeError:
|
|
||||||
FAIL
|
|
||||||
else:
|
|
||||||
FAIL
|
FAIL
|
||||||
|
|
||||||
.. [7]
|
.. [7]
|
||||||
|
@ -844,14 +832,10 @@ translates to::
|
||||||
|
|
||||||
if not isinstance($value, ClsName):
|
if not isinstance($value, ClsName):
|
||||||
FAIL
|
FAIL
|
||||||
$kind = type($value).__match_class__
|
try:
|
||||||
lif $kind & MATCH_ATTRIBUTES:
|
x = $value.a
|
||||||
try:
|
y = $value.b
|
||||||
x = $value.a
|
except AttributeError:
|
||||||
y = $value.b
|
|
||||||
except AttributeError:
|
|
||||||
FAIL
|
|
||||||
else:
|
|
||||||
FAIL
|
FAIL
|
||||||
|
|
||||||
.. [8]
|
.. [8]
|
||||||
|
@ -865,20 +849,16 @@ translates to::
|
||||||
|
|
||||||
if not isinstance($value, ClsName):
|
if not isinstance($value, ClsName):
|
||||||
FAIL
|
FAIL
|
||||||
$kind = type($value).__match_class__
|
$attrs = ClsName.__match_args__
|
||||||
if $kind & MATCH_ATTRIBUTES:
|
if len($attr) < 1:
|
||||||
$attrs = ClsName.__match_args__
|
raise TypeError(...)
|
||||||
if len($attr) < 1:
|
$positional_names = $attrs[:1]
|
||||||
|
try:
|
||||||
|
x = getattr($value, $attrs[0])
|
||||||
|
if "a" in $positional_names:
|
||||||
raise TypeError(...)
|
raise TypeError(...)
|
||||||
$positional_names = $attrs[:1]
|
y = $value.a
|
||||||
try:
|
except AttributeError:
|
||||||
x = getattr($value, $attrs[0])
|
|
||||||
if "a" in $positional_names:
|
|
||||||
raise TypeError(...)
|
|
||||||
y = $value.a
|
|
||||||
except AttributeError:
|
|
||||||
FAIL
|
|
||||||
else:
|
|
||||||
FAIL
|
FAIL
|
||||||
|
|
||||||
.. [9]
|
.. [9]
|
||||||
|
|
Loading…
Reference in New Issue