Python 3.10 match/case with constants

Viewed 1786

I tried to replace an if/elif/elif/.../else code block with the shorter match/case from Python 3.10. I have three constants defined and want to do something different for each one, so my code looks roughly like this:

>>> const_a = 1
>>> const_b = 2
>>> const_c = 3
>>> interface = const_b  # example
>>> match interface:
...     case const_a:
...             print("case a")
...     case const_b:
...             print("case b")
...     case const_c:
...             print("case c")

However, when running this code, there will be an Exception:

File "<stdin>", line 2
SyntaxError: name capture 'const_a' makes remaining patterns unreachable

What am I doing wrong?

2 Answers

The match...case is more than just a switch...case. From https://www.python.org/dev/peps/pep-0622/#patterns:

  • A capture pattern looks like x and is equivalent to an identical assignment target: it always matches and binds the variable with the given (simple) name.
  • A constant value pattern works like the literal but for certain named constants. Note that it must be a qualified (dotted) name, given the possible ambiguity with a capture pattern. It looks like Color.RED and only matches values equal to the corresponding value. It never binds.

So you're going to have to create an object that has those variables as attributes and use the qualified names in the match

import types

consts = types.SimpleNamespace()
consts.A = 1
consts.B = 2
consts.C = 3

interface = 2

match interface:
    case consts.A:
        print("A")
    case consts.B:
        print("B")
    case consts.C:
        print("C")

Which, as expected, prints B

For more information on why, see https://www.python.org/dev/peps/pep-0622/#alternatives-for-constant-value-pattern

[This answer is hyporthetical]

FWIW, that's how I'd have done match/case - seems much more intuitive to me than getting new variables from close to scratch and having no smart possibility to use variables to compare to or real constants:

Alternative match/case proposal


SOME_CONSTANT = 0
some_string = "qwert"

def example_matchcase (var):
    
    match var
        
    # simple literal case
    case 10:
        print("case 1")
    
    # basic check including another variable
    case len(some_string):
        print("case 2")
    
    # check against a constant
    case SOME_CONSTANT:
        print("case 3")
    
    # if-like statement
    case ? 0 < var < 10:
        print("case 4")
    
    case ? callable(var):
        print("case 5")
    case ? hasattr(var, 'isnumeric') and var.isnumeric():
        print("case 6")
    
    # slide statement
    case ? var < 50:
        print("case 7a")
        slide
    case ? var < 25:
        print("case 7b")
        slide
    case ? var < 10:
        print("case 7c")
        slide
    
    case ? var in (1,3,5,7):
        print("case 8 - early prime:", var)
    
    # 'else' instead of 'case _:'
    # more meaningful, _ looks like an unused variable and potentially conflicts with gettext
    else:
        print("unknown value")


""" real-world example where this would be very elegant """

INTERF_MUPDF = 0
INTERF_PDFIUM = 1
INTERF_POPPLER = 2

def render_thumbnails (interface):
    match interface
    case INTERF_MUPDF:
        from interfaces import InterfMupdf
        _interface = InterfMupdf()
    case INTERF_PDFIUM:
        from interfaces import InterfPdfium
        _interface = InterfPdfium()
    case INTERF_POPPLER:
        from interfaces import InterfPoppler
        _interface = InterfPoppler()
    case ? callable(interface):
        print("using a custom interface")
        _interface = interface
    else:
        raise Exception("`interface` must be a constant or a callable object")

Related