diff --git a/pep-0653.rst b/pep-0653.rst index dbf06cc587b..77edfb54f7f 100644 --- a/pep-0653.rst +++ b/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]