# Pattern matching on constants that aren't in a namespace (especially sentinels)

**URL:** <https://discuss.python.org/t/pattern-matching-on-constants-that-arent-in-a-namespace-especially-sentinels/107916>\
**Category:** Ideas\
**Created:** [June 26, 2026, 9:30am UTC](https://discuss.python.org/t/pattern-matching-on-constants-that-arent-in-a-namespace-especially-sentinels/107916 "2026-06-26T09:30:04Z")\
**Posts on this page:** 20\
**Page:** 3

<div class="post-metadata">

**Author:** ![sirosen](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/sirosen/32/8478_2.png) [@sirosen](https://discuss.python.org/u/sirosen)\
**Post date:** [June 30, 2026, 6:46pm UTC](https://discuss.python.org/t/pattern-matching-on-constants-that-arent-in-a-namespace-especially-sentinels/107916/42 "2026-06-30T18:46:10Z")

</div>

I earlier suggested keywords rather than symbolic syntax. I think I chose a bad name for the suggested keyword (`constant`), but it doesn’t matter: with further thought I’ve gone from neutral to -1 on this idea.

Do we really want a second way to spell

```python
case value if value is FOO:

```

?

I don’t see what we’d be adding that is better than what we already have, other than being shorter.

And I think this thread led me, and possibly others, to not think about some of the cases in which you may want pattern matching to lookup a name rather than binding to a name:

```python
b = "b"
match x:
    case ["a", .b, c]:
        print(c)

```

I don’t think that can be expressed reasonably by a keyword. But it can be with an if-guard:

```python
b = "b"
match x:
    case ["a", _b, c] if _b == b:
        print(c)

```

In that context, I wouldn’t want to use keywords to describe this. I don’t think the motivating example for this thread – use of sentinels as the top level match – is the right one to motivate a possible change.

---

<div class="post-metadata">

**Author:** ![xitop](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/xitop/32/13242_2.png) [@xitop](https://discuss.python.org/u/xitop)\
**Post date:** [June 30, 2026, 8:15pm UTC](https://discuss.python.org/t/pattern-matching-on-constants-that-arent-in-a-namespace-especially-sentinels/107916/43 "2026-06-30T20:15:57Z")

</div>

Somewhere between a joke and a real idea - no new keyword:

```python
    match color:
        case TRANSPARENT as is:
            print("it's transparent!")

```

---

<div class="post-metadata">

**Author:** ![Rosuav](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/rosuav/32/3429_2.png) [@Rosuav](https://discuss.python.org/u/Rosuav)\
**Post date:** [June 30, 2026, 8:17pm UTC](https://discuss.python.org/t/pattern-matching-on-constants-that-arent-in-a-namespace-especially-sentinels/107916/44 "2026-06-30T20:17:14Z")

</div>

> [@xitop](#):
>
> Somewhere between a joke and a real idea - no new keyword

I like it as a joke. Good one. 👍

---

<div class="post-metadata">

**Author:** ![blhsing](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/blhsing/32/25812_2.png) [@blhsing](https://discuss.python.org/u/blhsing)\
**Post date:** [July 1, 2026, 2:30am UTC](https://discuss.python.org/t/pattern-matching-on-constants-that-arent-in-a-namespace-especially-sentinels/107916/45 "2026-07-01T02:30:54Z")

</div>

> [@xitop](#):
>
> Somewhere between a joke and a real idea - no new keyword:
> 
> ```python
> match color:
> case TRANSPARENT as is:
> print("it's transparent!")
> 
> ```

That looks good if testing for a singular value is the only use case, but it’s not–ideally the new syntax should support equality tests in a nested match pattern, e.g.: `case TRANSPARENT | NEGATIVE:`.

EDIT: Actually it’d work if parenthesized: `case (TRANSPARENT as is) | (NEGATIVE as is):`, albeit a bit noisy.

EDIT 2: I think I’ve warmed up to it enough that I don’t even think it’s noisy anymore. I actually like the idea.

---

<div class="post-metadata">

**Author:** ![MRAB](https://avatars.discourse-cdn.com/v4/letter/m/e9c0ed/32.png) [@MRAB](https://discuss.python.org/u/MRAB)\
**Post date:** [July 1, 2026, 3:08pm UTC](https://discuss.python.org/t/pattern-matching-on-constants-that-arent-in-a-namespace-especially-sentinels/107916/46 "2026-07-01T15:08:51Z")

</div>

How about `TRANSPARENT as _`?

---

<div class="post-metadata">

**Author:** ![cdce8p](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/cdce8p/32/3575_2.png) [@cdce8p](https://discuss.python.org/u/cdce8p)\
**Post date:** [July 1, 2026, 6:31pm UTC](https://discuss.python.org/t/pattern-matching-on-constants-that-arent-in-a-namespace-especially-sentinels/107916/47 "2026-07-01T18:31:02Z")

</div>

> [@Rosuav](#):
>
> But how often do you match against specific unqualified values that aren’t sentinels? I’m still of the opinion that, between modules, enums, and string literals, the vast majority of cases should be handled. Do you have any non-toy examples you can link to where none of those is viable?

> [@pf\_moore](#):
>
> This does beg the question, what’s actually _wrong_ with `case x if x == spam:`?

A few examples from pylint. I’ve included the match case body since those do effect the readability as well IMO. _Note: The formatting is done with black. This could probably be better in some cases but that’s a different issue._

```py
    case nodes.Attribute(expr=nodes.Name(name=name)) if name == TYPING_MODULE:
        self._type_annotation_names.append(TYPING_MODULE)
        return

```

[source 1](https://github.com/pylint-dev/pylint/blob/v4.0.6/pylint/checkers/variables.py#L3064)

```py
    case nodes.Name(name=n) | nodes.Attribute(attrname=n) if n == typing_name:
        return True

```

[source 2](https://github.com/pylint-dev/pylint/blob/v4.0.6/pylint/checkers/utils.py#L1760)

```py
    case nodes.If(
        test=nodes.Compare(left=nodes.Name(name=n)) | nodes.Name(name=n)
    ) if (n == name):
        return True

```

[source 3](https://github.com/pylint-dev/pylint/blob/v4.0.6/pylint/extensions/code_style.py#L340-L342)

```py
    case nodes.Assign(
        targets=[
            nodes.Subscript(value=nodes.Name(name=name), slice=key_node)
        ],
        value=val_node,
    ) if (
        name == dict_name
    ):
        yield f"{key_node.as_string()}: {val_node.as_string()}"

```

[source 4](https://github.com/pylint-dev/pylint/blob/e91fca7d2839e260b3521b667c5fca86cfb56efa/pylint/extensions/dict_init_mutate.py#L83-L90)

While the pattern of MatchAs + guard clause isn’t used frequently, I think the examples above illustrate some of the issues with it pretty well. Some observations:

- If it is used, it’s usually nested inside other patterns.
- The variable and pattern often share similar names. That’s an issue as you could end up accidentally overwriting the variable. So you often end up with something like `n == name` or `name == dict_name` which isn’t ideal.
- Just looking at the guard clause alone, it’s difficult to known which of the two is the variable and which the newly assigned one. Looking back at the pattern, especially for the last example, it’s not immediately obvious where the variable itself is actually being checked.

There are additional limitations which haven’t been discussed yet.

- There are no guard clauses for individual patterns inside an `OR`. Something like the example below is invalid. It’s possible to work around it by splitting these into two separate cases but that might require duplication of the case body.

```py
        case nodes.Name(name=n) if n == typing_name | nodes.Class():

```

- The guard clause is only evaluated **after** the pattern itself matches. Not being able to match a variable itself, can cause unnecessary work if it’s obvious early on that the guard would evaluate to `False`. E.g. no need to check `targets` here if `val_node` isn’t equal to `node`:

```py
    case nodes.Assign(
        value=val_node,
        targets=[
            nodes.Subscript(value=nodes.Name(name="spam"), slice=key_node)
        ],
    ) if val_node == node:

```

- In theory it’s also possible to construct cases which will never match, though one might think it should have. That’s because the pattern isn’t reevaluated if the guard evaluates to `False`, it’s just skipped. Even if an alternative pattern would have matched as well and the guard would be `True` for that. E.g. in the example below only the second case will ever fully match:

```py
match {"a": None, "b": True, 1: "Hello", 2: "World"}:
    case {"a": var, 1: "Hello"} | {"b": var, 2: "World"} if var == True:
        print("Match 1")
    case {"a": True, 1: "Hello"} | {"b": True, 2: "World"}:
        print("Match 2")

```

* * *

Adding support for names to value patterns would help to deal with these. Regarding the proposed alternatives

- I agree that `global.` or `local.` prefixes would risk people wanting to use them elsewhere. Match already has it’s one mini syntax, so we might as well add something just for that too.
- Additional (soft-) keywords before / after case, like `case value ...` don’t really work for nested patterns.
- `case spam as is: ...` The `as` suggest that it’s an AS pattern were in reality it would be a value pattern.
- `==spam` / `is spam` As a student, I’d wonder why this is needed for names but not attributes. Similar for `$spam`.
- `value(spam)` is already defined as a class pattern.

I keep coming back to the leading dot `.spam`. Though I’m sympathetic to the argument that it can be hard to spot, it’s not more so than a name assignment with the existing uses in deeply nested patterns. In the end it’s a tradeoff, but one I think is worth it. It’s also easy to explain that a value pattern should always contain a “dot”.

---

<div class="post-metadata">

**Author:** ![BrenBarn](https://avatars.discourse-cdn.com/v4/letter/b/74df32/32.png) [@BrenBarn](https://discuss.python.org/u/BrenBarn)\
**Post date:** [July 2, 2026, 12:38am UTC](https://discuss.python.org/t/pattern-matching-on-constants-that-arent-in-a-namespace-especially-sentinels/107916/48 "2026-07-02T00:38:24Z")

</div>

What about new `mud` keyword so you could do `case clear as mud`?

---

<div class="post-metadata">

**Author:** ![blhsing](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/blhsing/32/25812_2.png) [@blhsing](https://discuss.python.org/u/blhsing)\
**Post date:** [July 2, 2026, 1:06am UTC](https://discuss.python.org/t/pattern-matching-on-constants-that-arent-in-a-namespace-especially-sentinels/107916/49 "2026-07-02T01:06:59Z")

</div>

> [@cdce8p](#):
>
> - Additional (soft-) keywords before / after case, like `case value ...` don’t really work for nested patterns.

Like I said above, it can work in nested patterns by enclosing the value pattern in parentheses, so if we are to introduce a new soft keyword `value` for the mini-language, this is how it can be used in a nested pattern:

```python
case (value TRANSPARENT) | (value NEGATIVE): ...
case (value is TRANSPARENT) | (value is NEGATIVE): ...

```

> [@cdce8p](#):
>
> - `case spam as is: ...` The `as` suggest that it’s an AS pattern were in reality it would be a value pattern.

We can use a keyword that has no existing meaning in the mini-language instead.

How about:

```python
case with TRANSPARENT: ...

```

as in “as is the case with `TRANSPARENT`”.

or:

```python
case for TRANSPARENT: ...

```

which is pretty self-explanatory.

> [@cdce8p](#):
>
> I keep coming back to the leading dot `.spam`.

`.spam` is just not very intuitively comprehensible to me. I hope that all the more intuitively readable options are fully explored before we settle for something like this.

As a bonus, a keyword-based notation can be generalized to allow evaluation of arbitrary expressions:

```python
case value STOP - 1:

```

which would look even more awkward with a leading-dot syntax (`case .(STOP - 1):` 😅).

---

<div class="post-metadata">

**Author:** ![a-reich](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/a-reich/32/5885_2.png) [@a-reich](https://discuss.python.org/u/a-reich)\
**Post date:** [July 2, 2026, 2:46am UTC](https://discuss.python.org/t/pattern-matching-on-constants-that-arent-in-a-namespace-especially-sentinels/107916/50 "2026-07-02T02:46:31Z")

</div>

As others have said, in comparing potential new syntax for “value patterns” to what’s currently writeable, we shouldn’t just look at simple examples like `case x if x == FOO`. You really want to look at scenarios with nested patterns, or repeatedly using the same value, or multiple different value patterns in one case.

How about this (admittedly artificial) example of checking if some JSON-like object contains certain values in an expected way:

```python
# using a SimpleNamespace we create just to have qualified names
def search1(username: str, teamname: str, data):
    ns = types.SimpleNamespace(username=username, teamname=teamname)
    match data:
        case ns.username | ns.teamname | {'user': ns.username} | {'team': ns.teamname}:
            return True
        case [(ns.username | ns.teamname), *_]:
            return True
        case _:
            return False

```

Let’s see how the “add a guard with ==” style looks:

```python
# don't use value patterns, just guards
def search2(username: str, teamname: str, data): 
    match data:
        case x if x == username or x == teamname:
            return True
        case {'user': x} if x == username:
            return True
        case {'team': x} if x == teamname:
            return True
        case [x, *_] if x == username or x == teamname:
            return True
        case _:
            return False

```

Not great. Although you can probably tweak the structure, I’m not sure you can make it nicer.

Now the proposed syntax:

```python
# not currently valid, but if we adopt leading-dot idea
# (substitute a different leading symbol or `value ...` if you prefer)
def search3(username: str, teamname: str, data):
    match data:
        case .username | .teamname | {'user': .username} | {'team': .teamname}:
            return True
        case [(.username | .teamname), *_]:
            return True
        case _:
            return False

```

IMO there’s an opportunity for worthwhile improvement here.

---

<div class="post-metadata">

**Author:** ![BenjyWiener](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/benjywiener/32/33309_2.png) [@BenjyWiener](https://discuss.python.org/u/BenjyWiener)\
**Post date:** [July 2, 2026, 5:53am UTC](https://discuss.python.org/t/pattern-matching-on-constants-that-arent-in-a-namespace-especially-sentinels/107916/51 "2026-07-02T05:53:07Z")

</div>

What about `case Some({some_expr}, x={var+1})`, f-string style? This is currently invalid syntax.

It could be read as matching a `set`, but this would be mitigated by:

1. Not allowing commas (tuples would be need to parenthesized),
2. Linters could warn about undefined names and hint that `set`s can’t be matched by contents,
3. `NameError` inside a value expression pattern could provide a similar hint in trackbacks.

---

<div class="post-metadata">

**Author:** ![tmk](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/tmk/32/10935_2.png) [@tmk](https://discuss.python.org/u/tmk)\
**Post date:** [July 2, 2026, 6:22am UTC](https://discuss.python.org/t/pattern-matching-on-constants-that-arent-in-a-namespace-especially-sentinels/107916/52 "2026-07-02T06:22:43Z")

</div>

Any syntax that uses keywords or other elaborate mechanisms has, IMO, the problem that it introduces an inconsistency with the existing “dotted name rule”:

```python
import enum

class Enum(enum Enum):
    VARIANT = enum.auto()

MY_SENTINEL = sentinel("MY_SENTINEL")

class Ns:
    OTHER_SEN = sentinel("OTHER_SEN")

x = ...
match x:
    case Enum.VARIANT: # just dotted name
        pass
    case keyword MY_SENTINEL: # why different?
        pass
    case Ns.OTHER_SEN: # just dotted name again?
        pass

```

So I think for consistency, the new syntax should be of the form

```python
<scope>.variable_name

```

And an _empty_ scope (i.e., leading-dot syntax) still makes the most sense to me here but it’s possible that a better scope name exists.

---

<div class="post-metadata">

**Author:** ![Rosuav](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/rosuav/32/3429_2.png) [@Rosuav](https://discuss.python.org/u/Rosuav)\
**Post date:** [July 2, 2026, 6:55am UTC](https://discuss.python.org/t/pattern-matching-on-constants-that-arent-in-a-namespace-especially-sentinels/107916/53 "2026-07-02T06:55:46Z")

</div>

Thanks for the examples! Taking each in turn:

> [@cdce8p](#):
>
> ```python
> case nodes.Attribute(expr=nodes.Name(name=name)) if name == TYPING_MODULE:
> self._type_annotation_names.append(TYPING_MODULE)
> return
> 
> ```
> 
> [source 1](https://github.com/pylint-dev/pylint/blob/v4.0.6/pylint/checkers/variables.py#L3064)

This seems to be using `TYPING_MODULE = "typing"` from [line 56](https://github.com/pylint-dev/pylint/blob/main/pylint/checkers/variables.py#L56) where there are several other constants being assigned. It would possibly be a backward-incompatible change to make these into a set of class attributes, but if that can be done without breaking things, it would solve the problem:

```python
class Const:
    FUTURE = " __future__"
    ...
    TYPING_MODULE = "typing"

```

Though personally, I don’t really see the value of having constants for these. String literals should be fine. Unless people expect that the name of the ` __future__ ` module will change in the future, or something. I’d have to say it’s rather unlikely, but then again, [Valve did leave open the possibility of M\_PI changing](https://github.com/ValveSoftware/halflife/blob/b1b5cf5892918535619b2937bb927e46cb097ba1/public/basetypes.h#L41).

> [@cdce8p](#):
>
> [source 2](https://github.com/pylint-dev/pylint/blob/v4.0.6/pylint/checkers/utils.py#L1760)
> 
> ```python
> case nodes.If(
> test=nodes.Compare(left=nodes.Name(name=n)) | nodes.Name(name=n)
> ) if (n == name):
> return True
> 
> ```

This is a match block with a single case in it that then returns True, and if it fails to match, it returns False. Seems like a code smell to me. There’s already a couple of `isinstance` checks above; why not two more here?

```python
if isinstance(annotation, nodes.Name): return annotation.name == typing_name
if isinstance(annotation, nodes.Attribute): return annotation.attrname == typing_name
return False

```

> [@cdce8p](#):
>
> ```python
> case nodes.If(
> test=nodes.Compare(left=nodes.Name(name=n)) | nodes.Name(name=n)
> ) if (n == name):
> return True
> 
> ```
> 
> [source 3](https://github.com/pylint-dev/pylint/blob/v4.0.6/pylint/extensions/code_style.py#L340-L342)

Another one-case match block that comes immediately after `if` statements that do `isinstance` checks. Not sure why this isn’t done the same way.

> [@cdce8p](#):
>
> ```python
> case nodes.Assign(
> targets=[
> nodes.Subscript(value=nodes.Name(name=name), slice=key_node)
> ],
> value=val_node,
> ) if (
> name == dict_name
> ):
> yield f"{key_node.as_string()}: {val_node.as_string()}"
> 
> ```
> 
> [source 4](https://github.com/pylint-dev/pylint/blob/e91fca7d2839e260b3521b667c5fca86cfb56efa/pylint/extensions/dict_init_mutate.py#L83-L90)

This one’s the most interesting. It has real capturing going on (the `key_node` and `val_node` get used in the `yield`), in addition to the capture-for-comparison. It’s also not a constant (`dict_name` is a function parameter - in fact, it’s a nonlocal since this is a nested function) so it can’t be replaced with a literal or stuck into a class. So I think this one is the strongest recommendation for having syntax for this.

However, that’s still only _one_ example. Unless there are a lot of other cases like it, the slightly clunky existing syntax is good enough for the rare occasions.

> [@cdce8p](#):
>
> - In theory it’s also possible to construct cases which will never match, though one might think it should have. That’s because the pattern isn’t reevaluated if the guard evaluates to `False`, it’s just skipped. Even if an alternative pattern would have matched as well and the guard would be `True` for that. E.g. in the example below only the second case will ever fully match:
> 
> ```python
> match {"a": None, "b": True, 1: "Hello", 2: "World"}:
> case {"a": var, 1: "Hello"} | {"b": var, 2: "World"} if var == True:
> print("Match 1")
> case {"a": True, 1: "Hello"} | {"b": True, 2: "World"}:
> print("Match 2")
> 
> ```
> 
> TBH I don’t think this is particularly likely, but if anyone does find a real-world example where this can happen, it’s probably going to be sufficiently-gnarly code that it’s hard to read in _any_ form.

---

<div class="post-metadata">

**Author:** ![BenjyWiener](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/benjywiener/32/33309_2.png) [@BenjyWiener](https://discuss.python.org/u/BenjyWiener)\
**Post date:** [July 2, 2026, 7:23am UTC](https://discuss.python.org/t/pattern-matching-on-constants-that-arent-in-a-namespace-especially-sentinels/107916/54 "2026-07-02T07:23:16Z")

</div>

> [@Rosuav](#):
>
> String literals should be fine. Unless people expect that the name of the ` __future__ ` module will change in the future, or something.

It’s not so much about the name changing. I use this pattern because my linter will freak out if I write `FUTRE`, but couldn’t care less about `' __futre__'`.

---

<div class="post-metadata">

**Author:** ![Rosuav](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/rosuav/32/3429_2.png) [@Rosuav](https://discuss.python.org/u/Rosuav)\
**Post date:** [July 2, 2026, 7:38am UTC](https://discuss.python.org/t/pattern-matching-on-constants-that-arent-in-a-namespace-especially-sentinels/107916/55 "2026-07-02T07:38:56Z")

</div>

> [@BenjyWiener](#):
>
> It’s not so much about the name changing. I use this pattern because my linter will freak out if I write `FUTRE`, but couldn’t care less about `' __futre__'`.

Ehh, fair enough I guess, but it’s a lot of hassle to catch typos, and still can only catch a very specific category of them. I don’t think it’s a use-case that can justify new syntax, especially given that the “stick it in a class” option works fine for those.

---

<div class="post-metadata">

**Author:** ![elis.byberi](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/elis.byberi/32/35937_2.png) [@elis.byberi](https://discuss.python.org/u/elis.byberi)\
**Post date:** [July 2, 2026, 8:16am UTC](https://discuss.python.org/t/pattern-matching-on-constants-that-arent-in-a-namespace-especially-sentinels/107916/56 "2026-07-02T08:16:07Z")

</div>

> [@cdce8p](#):
>
> - The guard clause is only evaluated **after** the pattern itself matches. Not being able to match a variable itself, can cause unnecessary work if it’s obvious early on that the guard would evaluate to `False`. E.g. no need to check `targets` here if `val_node` isn’t equal to `node`:

This is a nice optimization to have, but it is not related to the syntax in question 🙂. I am not sure it can be optimized when using guards, i.e., captures happen first, then guards are applied.

* * *

> [@a-reich](#):
>
> I’m not sure you can make it nicer.

I don’t know if this approach looks nicer, but it keeps the structure clear:

```python
def search4(username: str, teamname: str, data):
    if data in (username, teamname):
        return True

    match data:
        case {'user': user, 'team': team}:
            return bool({user, team} & {username, teamname})

        case [x, *_]:
            return x in (username, teamname)

    return False

data = 'Bob'
print(search4('Bob', 'Transceiver', data, )) # True

data = {'user': 'Bob', 'team': 'Transceiver'}
print(search4('Bob', 'Transceiver', data)) # True
print(search4('Bob', 'Wonderland', data)) # True
print(search4('Alice', 'Wonderland', data)) # False

data = ['Bob', *range(10)]
print(search4('Bob', 'Transceiver', data)) # True
print(search4('Bob', 'Wonderland', data)) # True
print(search4('Alice', 'Wonderland', data)) # False

```

Note the use of `if`; `match` is not meant to replace `if`, `elif`, and `else`.

---

<div class="post-metadata">

**Author:** ![cdce8p](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/cdce8p/32/3575_2.png) [@cdce8p](https://discuss.python.org/u/cdce8p)\
**Post date:** [July 2, 2026, 11:32am UTC](https://discuss.python.org/t/pattern-matching-on-constants-that-arent-in-a-namespace-especially-sentinels/107916/57 "2026-07-02T11:32:30Z")

</div>

Thanks for the reply @Rosuav!

> [@Rosuav](#):
>
> This seems to be using `TYPING_MODULE = "typing"` from [line 56](https://github.com/pylint-dev/pylint/blob/main/pylint/checkers/variables.py#L56) where there are several other constants being assigned. It would possibly be a backward-incompatible change to make these into a set of class attributes,

The constraint I had when I added the match statement was that it needed to be fully backwards compatible. There shouldn’t be any code changes besides moving from `if` to `match`. As it was pointed out multiple times already, of course it’s possible to rewrite existing code so it fits within the constraints of the current syntax but that misses the point here unfortunately.

> [@Rosuav](#):
>
> > [@cdce8p](#):
> >
> > ```python
> > case nodes.Name(name=n) | nodes.Attribute(attrname=n) if n == typing_name:
> > return True
> > 
> > ```
> > 
> > [source 2](https://github.com/pylint-dev/pylint/blob/v4.0.6/pylint/checkers/utils.py#L1760)
> 
> This is a match block with a single case in it that then returns True, and if it fails to match, it returns False. Seems like a code smell to me. There’s already a couple of `isinstance` checks above; why not two more here?
> 
> ```python
> if isinstance(annotation, nodes.Name): return annotation.name == typing_name
> if isinstance(annotation, nodes.Attribute): return annotation.attrname == typing_name
> return False
> 
> ```

You’re right that it’s possible to use `if` statements here. This was the example before moving to a `match` statement:

```py
if (isinstance(annotation, nodes.Name) and annotation.name == typing_name) or (
    isinstance(annotation, nodes.Attribute) and annotation.attrname == typing_name
):
    return True

```

IMO though, even with the guard expression, the match statement is easier to read and comprehend. Others thought so too, otherwise it wouldn’t have been merged. Regarding the single case, I don’t think it’s a code spell. Sure it could be improved, maybe at some point a match expression is added, there has been some initial discussion in this [topic](https://discuss.python.org/t/on-the-case-of-single-line-match-statements/85174) already. However, even the first match [PEP 622](https://peps.python.org/pep-0622/#one-off-syntax-variant) recognized that single case statements would be common and even added it to the deferred ideas section.

> [@Rosuav](#):
>
> > [@cdce8p](#):
> >
> > ```python
> > case nodes.If(
> > test=nodes.Compare(left=nodes.Name(name=n)) | nodes.Name(name=n)
> > ) if (n == name):
> > return True
> > 
> > ```
> > 
> > [source 3](https://github.com/pylint-dev/pylint/blob/v4.0.6/pylint/extensions/code_style.py#L340-L342)
> 
> Another one-case match block that comes immediately after `if` statements that do `isinstance` checks. Not sure why this isn’t done the same way.

This was even worse as an `if` statement. IMO a prime example where `match` statements really shine and improve readability.

```py
if (
    next_if_node is not None
    and (
        (
            isinstance(next_if_node.test, nodes.Compare)
            and isinstance(next_if_node.test.left, nodes.Name)
            and next_if_node.test.left.name == name
        )
        or (
            isinstance(next_if_node.test, nodes.Name)
            and next_if_node.test.name == name
        )
    )
):
    return True

```

> [@Rosuav](#):
>
> > [@cdce8p](#):
> >
> > ```python
> > match {"a": None, "b": True, 1: "Hello", 2: "World"}:
> > case {"a": var, 1: "Hello"} | {"b": var, 2: "World"} if var == True:
> > print("Match 1")
> > case {"a": True, 1: "Hello"} | {"b": True, 2: "World"}:
> > print("Match 2")
> > 
> > ```
> 
> TBH I don’t think this is particularly likely, but if anyone does find a real-world example where this can happen, it’s probably going to be sufficiently-gnarly code that it’s hard to read in _any_ form.

I agree that I wouldn’t write this myself. However it was only meant to show a potential pitfall which can happen with other code as well. True, the easiest workaround would be to split these up into two separate cases. However, that will at minimum require duplicating the case body and won’t always make sense. Just consider that the OR pattern is nested 2 or 3 layers deep.

–  
In summary, of course it’s possible to write any example using the existing syntax. I’m not arguing here that everybody should go out there and add the leading-dot everywhere if it is added. However, it expands the toolbox of what is possible to read and write easily with the match statement and that I believe is a worthy improvement.

---

<div class="post-metadata">

**Author:** ![cdce8p](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/cdce8p/32/3575_2.png) [@cdce8p](https://discuss.python.org/u/cdce8p)\
**Post date:** [July 2, 2026, 11:38am UTC](https://discuss.python.org/t/pattern-matching-on-constants-that-arent-in-a-namespace-especially-sentinels/107916/58 "2026-07-02T11:38:13Z")

</div>

> [@elis.byberi](#):
>
> > [@cdce8p](#):
> >
> > - The guard clause is only evaluated **after** the pattern itself matches. Not being able to match a variable itself, can cause unnecessary work if it’s obvious early on that the guard would evaluate to `False`. E.g. no need to check `targets` here if `val_node` isn’t equal to `node`:
> 
> This is a nice optimization to have, but it is not related to the syntax in question 🙂. I am not sure it can be optimized when using guards, i.e., captures happen first, then guards are applied.

It can be optimized though if the value pattern supports simple names and not just attributes, e.g. with `.node` 🙂 That would be checked immediately.

---

<div class="post-metadata">

**Author:** ![a-reich](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/a-reich/32/5885_2.png) [@a-reich](https://discuss.python.org/u/a-reich)\
**Post date:** [July 2, 2026, 10:31pm UTC](https://discuss.python.org/t/pattern-matching-on-constants-that-arent-in-a-namespace-especially-sentinels/107916/59 "2026-07-02T22:31:17Z")

</div>

> [@elis.byberi](#):
>
> ```python
> def search4(username: str, teamname: str, data):
> if data in (username, teamname):
> return True
> 
> match data:
> case {'user': user, 'team': team}:
> return bool({user, team} & {username, teamname})
> 
> case [x, *_]:
> return x in (username, teamname)
> 
> return False
> 
> ```

FWIW, this is not equivalent: `search4('myuser','myteam',data={'user': 'myteam', 'team': 'foo'})` is True, while search1 returns False.

---

<div class="post-metadata">

**Author:** ![elis.byberi](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/elis.byberi/32/35937_2.png) [@elis.byberi](https://discuss.python.org/u/elis.byberi)\
**Post date:** [July 3, 2026, 12:57pm UTC](https://discuss.python.org/t/pattern-matching-on-constants-that-arent-in-a-namespace-especially-sentinels/107916/60 "2026-07-03T12:57:19Z")

</div>

Yes, it should be:

```python
"""
        case {'user': user, 'team': team}:
            return user == username or team == teamname
"""

print(search4('myuser','myteam',data={'user': 'myteam', 'team': 'foo'})) # False

```

I tried to make it less verbose than using if/else, but I forgot about the constraint of validating the user and team against their respective variables.

---

<div class="post-metadata">

**Author:** ![tmk](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/tmk/32/10935_2.png) [@tmk](https://discuss.python.org/u/tmk)\
**Post date:** [July 6, 2026, 11:23am UTC](https://discuss.python.org/t/pattern-matching-on-constants-that-arent-in-a-namespace-especially-sentinels/107916/61 "2026-07-06T11:23:37Z")

</div>

To gauge sentiment, I created a poll to ask which syntax people prefer.

I’ve used the same example for all snippets below. (Thanks to @cdce8p and @a-reich whose posts have been the source for them.)

This is the context for the example:

```python
MISSING = sentinel("MISSING")
teamname = "Team A"
attrname = "var"

```

### 1. Status quo

This already works in Python today:

```python
match data:
    case _x if _x == MISSING:
        pass
    case {'team': name} if name == teamname:
        pass
    case ast.Attribute(attrname=n) if n == attrname:
        pass

```

## Generalizations of the existing dot syntax

`namespace.constant` already works with the current syntax, so these proposals try to generalize it.

### 2. Leading dot

```python
match data:
    case .MISSING:
        pass
    case {'team': .teamname}:
        pass
    case ast.Attribute(attrname=.attrname):
        pass

```

### 3. `global`/`local`

(requires `local` as a new (soft?) keyword)

```python
match data:
    case global.MISSING:
        pass
    case {'team': local.teamname}:
        pass
    case ast.Attribute(attrname=local.attrname):
        pass

```

### 4. Only `global`

(it’s already a keyword and covers the sentinel use-case)

```python
match data:
    case global.MISSING:
        pass
    case {'team': name} if name == teamname:
        pass
    case ast.Attribute(attrname=n) if n == attrname:
        pass

```

### 5. `globals`/`locals`

(these are currently builtins)

```python
match data:
    case globals.MISSING:
        pass
    case {'team': locals.teamname}:
        pass
    case ast.Attribute(attrname=locals.attrname):
        pass

```

## Dotless syntax proposals

These proposals are not modeled after the existing dot-based syntax.

### 6. prefixed `is`

```python
match data:
    case is MISSING:
        pass
    case {'team': is teamname}:
        pass
    case ast.Attribute(attrname is attrname):
        pass

```

### 7. prefixed `==`

```python
match data:
    case ==MISSING:
        pass
    case {'team': ==teamname}:
        pass
    case ast.Attribute(attrname==attrname):
        pass

```

### 8. `as is`

```python
match data:
    case MISSING as is:
        pass
    case {'team': teamname as is}:
        pass
    case ast.Attribute(attrname=attrname as is):
        pass

```

### 9. Other keyword-based syntax

For example with `value`

```python
match data:
    case value MISSING:
        pass
    case {'team': value teamname}:
        pass
    case ast.Attribute(attrname=value attrname):
        pass

```

Other keywords proposed here: `with`, `for`.

### 10. Curly brackets

(this is currently invalid syntax, so it would be available to be reinterpreted)

```python
match data:
    case {MISSING}:
        pass
    case {'team': {teamname}}:
        pass
    case ast.Attribute(attrname={attrname}):
        pass

```

_Poll ([view on site](https://discuss.python.org/t/pattern-matching-on-constants-that-arent-in-a-namespace-especially-sentinels/107916/61))_

[Previous page](https://discuss.python.org/t/pattern-matching-on-constants-that-arent-in-a-namespace-especially-sentinels/107916.md?page=2)

[Next page](https://discuss.python.org/t/pattern-matching-on-constants-that-arent-in-a-namespace-especially-sentinels/107916.md?page=4)
