










value NAME, constant case NAME:)global.NAME, nonlocal.NAME)This PEP enables the use of non-attribute names as value patterns in match statements. A name prefixed with a dot is looked up using the normal name resolution rules and compared to the match subject by equality in the same manner as existing value patterns, instead of being treated as a capture pattern. This makes it possible to match local variables and global variables defined in the same module without using a guard clause.
def get_book_titles_for_author(author: str): for book in BOOKS: match book: case {"title": title, "author": .author}: yield title
When the match statement was proposed in PEP 634, the decision was made to only support dotted names, i.e. attributes, in value patterns because it is not possible to differentiate simple (undotted) names from capture patterns. However, this decision makes it especially difficult to match something against local variables, e.g. function arguments. The current workaround for this limitation is to combine a name capture pattern with an explicit guard clause.
BOOKS: list[dict[str, str]] def get_book_titles_for_author(author: str): for book in BOOKS: match book: case {"title": title, "author": book_author} if ( book_author == author ): yield title
While this works, it is unnecessarily difficult to read and write, especially if the match case gets more complex. Furthermore, it has some additional limitations:
author. This makes it necessary
to choose a different, often suboptimal name for the name capture only to
avoid accidentally overwriting the variable.OR patterns is limited because OR
patterns require that name captures are defined in all alternatives. This
can make it necessary to duplicate the case body if an alternative does not
need the name capture.This PEP picks up on a deferred suggestion from PEP 635 to use a leading dot for value patterns with simple (undotted) names. The example above could then be written as:
def get_book_titles_for_author(author: str): for book in BOOKS: match book: case {"title": title, "author": .author}: yield title
A similar issue exists when trying to match a subject against global variables, in particular those defined in the same module. While other workarounds exist to be able to use the attribute syntax — for example the variable could be moved to another module or be wrapped by an enum or namespace — it is often desired to keep existing code as is when using a match statement.
This might especially be the case for sentinels added in PEP 661. Sentinels are likely to be used in some way in the same module they are defined in, but with the current syntax it is not possible to match against them without using workarounds like the guard clause.
The value pattern will be extended to support simple names if they are prefixed by a leading dot. The lookup is performed following the standard Python name resolution rules.
The pattern grammar of PEP 634 is extended. The value_pattern rule
gains a second alternative:
value_pattern: | attr !('.' | '(' | '=') | '.' NAME !('.' | '(' | '=')
and the key of a mapping pattern item may likewise be a leading-dot name:
key_value_pattern: | (literal_expr | attr | '.' NAME) ':' pattern
The new form consists of exactly one dot followed by exactly one identifier.
Attribute chains after a leading dot (.ns.CONST) are not permitted.
The existing rule is: “a value pattern containing a dot is a lookup; a name pattern without a dot is a binding”. The leading-dot form extends this rule to an unqualified name, for which the portion to the left of the dot is empty:
| Pattern | Meaning |
|---|---|
Color.RED |
lookup (status quo) |
ui.colors.RED |
lookup (status quo) |
.RED |
lookup (new) |
red |
binding (status quo) |
Under this proposal, the rule can be stated as: “a dot anywhere in a name pattern means lookup”. The proposal does not add a keyword or operator, and it does not change the meaning of any existing pattern.
This rule was considered in PEP 635 but was deferred as no consensus could be reached at the time and it could always be added later without backward-compatibility issues. The predecessor PEP for pattern matching PEP 622 used this rule.
In a poll accompanying the discussion thread for this PEP, respondents could approve of multiple options. The majority (71%) preferred the leading-dot syntax over the alternatives (discussed below) and over the status quo.
One concern noted in PEP 635 was that the dot “would not be a visible-enough marker”. We disagree.
We believe that the ease of teaching and using the rule outweighs the concerns about visibility. Alternatives such as the guard clause workaround are often more difficult to read, in particular in more complex match cases where the guard clause is separate from the value pattern.
Furthermore, Python already uses a leading dot in relative imports:
from .config import DEFAULTS and from config import
DEFAULTS differ only by the dot, and both forms are valid.
Additionally, syntax highlighters could distinguish capture patterns from value
patterns and make the difference between NAME and .NAME more visible.
Other languages, like Swift, also use leading dots in pattern matching.
The change is fully backwards compatible. So far using .name raised a
SyntaxError.
There are no new security implications from this proposal.
The rule presented in the PEP 636 tutorial can be stated as follows:
In a pattern, a name with a dot is looked up and compared; a name without a dot captures the subject.
The leading-dot form can be introduced as “a value pattern whose namespace part
is empty”: you write helpers.MISSING when the constant lives in a separate
namespace and .MISSING when it does not.
MISSING = sentinel('MISSING') match value: case .MISSING: # value pattern; looked up and compared ... case found: # capture pattern; always matches and binds ...
Documentation for the match statement will be updated to include the
leading-dot syntax.
A reference implementation is available at https://github.com/cdce8p/cpython/tree/pep845-match-leading-dot. An online demo can be tested at https://pep845-demo.pages.dev.
Alternative one-character or operator-like markers were proposed in the
original discussions and again in the thread for this PEP: ^CONSTANT (the
“pin” operator, as in Elixir), $CONSTANT, ?CONSTANT, ==CONSTANT,
{CONSTANT}, and backticks. None of these markers is currently used for
lookup in Python patterns. By contrast, a dot is already part of every dotted
value pattern. Curly braces could be confused with mapping patterns, while
==CONSTANT could imply support for other comparison operators. General
comparison patterns are outside the scope of this PEP, as discussed below. In
the community poll referenced in Why a leading dot, each of these options
received fewer approvals than the leading-dot form.
value NAME, constant case NAME:)Spellings such as case value MISSING: or a modified constant case
MISSING: clause are more visible than a dot, which was their primary
advantage in the discussion. These forms would add new soft keywords or
keyword-like syntax. A pattern-level form such as case Node(kind=value
LEAF): is less concise when nested, while a clause-level form cannot mark one
subpattern within a larger pattern.
global.NAME, nonlocal.NAME)Reusing the global and nonlocal keywords as pseudo-namespaces would
make the scope of the lookup explicit. It would also couple each pattern to
the scope in which the constant is defined. For example, a module-level
constant would use global.NAME, but moving it into an enclosing function
would require changing its patterns to nonlocal.NAME. These forms do not
cover local names or builtins. The proposed extension local.NAME would
require a new keyword because local is currently an ordinary identifier.
Standard name resolution covers all of these scopes without additional syntax.
In addition, nonlocal.NAME does not have an equivalent expression form
elsewhere in Python.
Treating UPPER_CASE names as constants was considered and rejected during
the original pattern-matching design: no other part of core Python attaches
semantics to the case of an identifier, and identifiers in scripts without a
case distinction (e.g. CJK characters) could never be matched as values.
.ns.CONST would be exactly equivalent to ns.CONST, providing a second
spelling for existing syntax without adding any capability. Restricting the new
form to a single identifier avoids this duplication.
Since PEP 661 gives each sentinel a distinct type, matching could be
supported through class patterns, or sentinels could be special-cased as
quasi-literals like None. But the problem is not specific to sentinels:
any unqualified constant (a numeric constant, an interned default object, an
enum member imported with from module import MEMBER) has the same issue.
Solving it for one kind of value would leave other unqualified constants
unsupported and would add a sentinel-specific exception to the pattern grammar.
Revisiting the fundamental PEP 634 decision that a bare name is a capture pattern would be a breaking change and is therefore rejected.
A leading dot in relative imports means “relative to the current package”, and
some participants in the discussion noted that case .NAME: would similarly
suggest “in the current namespace” and could therefore imply that names in
outer scopes are excluded. In this proposal, the dot does not select a scope.
The name after the dot is resolved as it would be in an ordinary expression at
that location (local, enclosing, global, then builtin scope). The dot
determines only whether the pattern performs a lookup or a binding. We believe
this is the more useful behavior and that it is easier to teach and understand
than a scope-restricted lookup.
Resolution in the builtin scope also permits matching values that are otherwise
available only as bare names. NotImplemented and Ellipsis are constants
that — unlike None, True and False — are ordinary names rather
than keywords. They consequently have capture semantics when used as bare
patterns and cannot be qualified without importing builtins. The same
applies to builtin types when the type object itself is the value being
matched, for example when dispatching on a type stored in an annotation or
configuration value:
match target_type: case .int | .float: return NumericColumn(target_type) case .str: return TextColumn()
Note the difference from the class pattern case int():, which matches
instances of int: the value pattern case .int: matches the type
object itself.
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。