Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 845 – Leading-Dot Value Patterns

PEP 845 – Leading-Dot Value Patterns

Author:
Thomas Kehrenberg <tmke8 at posteo.net>, Marc Mueller
Sponsor:
Ethan Furman <ethan at stoneleaf.us>
Discussions-To:
Discourse thread
Status:
Draft
Type:
Standards Track
Created:
25-Aug-2026
Python-Version:
3.16
Post-History:
17-Sep-2026

Table of Contents

Abstract

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

Motivation

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:

  • As a workaround, it might not get taught together with the value pattern. In particular developers new to the match statement might find it difficult to come up with it at first.
  • The guard clause is separate from the value pattern. For deeply nested patterns, this increases the complexity while reading the match case. It is necessary to keep track of all capture patterns mentally just for it to be used in the guard clause. At which point it is not obvious whether or not the name is also used in the case body as well.
  • The name being checked is often closely related or even the same as the variable. In the example above both are author. This makes it necessary to choose a different, often suboptimal name for the name capture only to avoid accidentally overwriting the variable.
  • Due to the similar names, it is also frequently not possible to know only by reading the guard clause which is the name and which the variable being checked.
  • Combining the workaround with 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.
  • The guard clause is only checked after the pattern itself matches. Especially for complex patterns, this can lead to unnecessary work when the name capture is followed by other patterns. As the capture always succeeds, the other patterns are evaluated even if it is obvious to the outside observer that the guard will fail.

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

Matching global variables

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.

Specification

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.

Grammar

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.

Rationale

Why a leading dot

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.

Visibility of the dot

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.

Backwards Compatibility

The change is fully backwards compatible. So far using .name raised a SyntaxError.

Security Implications

There are no new security implications from this proposal.

How to Teach This

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.

Reference Implementation

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.

Rejected Ideas

Other sigils

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.

Keyword-based markers (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.

Scope-qualified lookups (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.

Distinguishing by case of the name

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.

Allowing attribute chains after the leading dot

.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.

Special-casing sentinels only

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.

Making lookup the default and marking captures instead

Revisiting the fundamental PEP 634 decision that a bare name is a capture pattern would be a breaking change and is therefore rejected.

Restricting the leading-dot syntax to the current scope

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.