Python Literal Pattern: Match Exact Values
python literal pattern: Learn how to use literal patterns in Python's match statement to match exact values like integers, strings, booleans, and None, with practical...
The match statement in Python 3.10 introduced structural pattern matching, and the simplest form of pattern is the literal pattern. A literal pattern matches an exact value, such as a number, a string, a boolean, or None. It behaves like an equality check, but it is written as part of a pattern that can be combined with other pattern features like OR patterns and guards. Understanding the python literal pattern is the first step to using match effectively for value-based dispatch.
What Is a Literal Pattern in Python's match Statement?
A literal pattern is a pattern that matches only if the subject equals the literal value. In a match statement, each case clause contains a pattern. When the pattern is a literal, Python compares the subject to that literal using equality. For example:
def describe(value): match value: case 0: return "zero" case 1: return "one" case "hello": return "greeting" case None: return "nothing" case _: return "something else"
The case _ is a wildcard pattern that matches any value. The literal patterns appear before it, so they take precedence in the order they are written. The match statement evaluates patterns top to bottom, and the first matching case is executed.
Literal patterns are not limited to integers and strings. They also work with booleans, None, and even enum members, as long as the comparison uses == semantics. For instance, case True matches only the boolean True, not the integer 1, because True == 1 is True in Python, but pattern matching uses equality without type coercion? Actually, pattern matching uses == semantics, so True == 1 is True. This can lead to subtle behavior. We'll cover that in the edge cases section.
Matching Basic Literal Types
The most common literal patterns are integers, strings, booleans, and None. Here is a more complete example:
def classify(value): match value: case 42: return "the answer" case "error": return "failure" case True: return "boolean true" case False: return "boolean false" case None: return "none" case _: return "other"
Notice that case True and case False are separate. If you intended to match any boolean, you would use an OR pattern or a guard, not a literal pattern. Literal patterns are exact-value matches, not type checks.
Floating-point literals are also allowed, but they carry the usual precision concerns. case 0.1 matches only values that compare equal to 0.1, which can be surprising due to binary representation. In most cases, matching floats with literal patterns is not recommended unless you control the input exactly.
Using OR Patterns to Match Multiple Literals
You can combine multiple literals in a single case using the OR operator |. This is useful when several distinct values should trigger the same behavior:
def status_code(code): match code: case 200 | 201 | 204: return "success" case 400 | 404: return "client error" case 500 | 502 | 503: return "server error" case _: return "unknown"
The OR pattern is evaluated left to right, and the first literal that matches wins. This is more readable than repeating the same case body for each value or using a long if condition.
OR patterns can also combine different types, such as case "red" | "blue" | 1:, though mixing types in a single case may indicate a design issue. Use it when the values are semantically related.
Combining Literal Patterns with Guards
A guard is an if condition attached to a pattern. It refines the match beyond the pattern itself. Literal patterns often need guards when you want to match a value only under a certain condition, or when you want to match a range of values that cannot be expressed as a single literal.
def classify_age(age): match age: case 0: return "newborn" case n if n < 18: return "minor" case n if n >= 18 and n < 65: return "adult" case n if n >= 65: return "senior" case _: return "invalid"
Here, case 0 is a literal pattern, and the subsequent cases use capture patterns with guards. The guard is evaluated only after the pattern itself matches. For literal patterns, a guard is rarely needed because the literal already restricts the value, but you might combine a literal with a guard to check an additional property, such as a string that matches a literal but also passes a length check:
match command: case "start" if verbose: print("Starting with verbose output") case "start": print("Starting")
This allows the same literal to have different behavior based on an external condition. The order matters: the guarded case must come before the unguarded one, because the first matching case is used.
Combining with Other Patterns: Capture and Wildcard
Literal patterns are often used alongside capture patterns and the wildcard _. A capture pattern binds the subject to a variable, while a literal pattern requires an exact match. You can mix them in a single match statement to handle both specific values and general cases.
def process(value): match value: case 0: return "zero" case x: return f"nonzero: {x}"
In this example, case 0 is a literal pattern, and case x is a capture pattern that matches any value and binds it to x. The capture pattern acts as a catch-all, similar to _, but it also gives you access to the value.
The wildcard _ is a special capture pattern that does not bind. Use it when you do not need the value:
match value: case 0: print("zero") case _: print("non-zero")
Literal patterns can also be nested inside other patterns, such as sequence patterns or mapping patterns. For example:
match point: case (0, 0): print("origin") case (0, y): print(f"on y-axis at {y}") case (x, 0): print(f"on x-axis at {x}") case (x, y): print(f"at ({x}, {y})")
Here, (0, 0) uses literal patterns inside a sequence pattern. This is a powerful way to match structured data with exact values.
Common Mistakes and Edge Cases
Literal Patterns vs. Variables
A common mistake is writing a variable name in a case expecting it to match the variable's value. In pattern matching, a bare name is a capture pattern, not a literal. For example:
threshold = 10 match value: case threshold: print("matched")
This does not compare value to 10. Instead, it captures value into a new variable named threshold, shadowing the outer variable. To match the value of a variable, you need a dotted name (like SomeClass.VALUE) or a guard. Since literals are constants, you cannot use a variable as a literal pattern directly. If you need to match against a variable's value, use a guard:
threshold = 10 match value: case x if x == threshold: print("matched")
Equality vs. Identity
Literal patterns use equality (==), not identity (is). This means that case 1 will match True because True == 1 is True. Similarly, case 1.0 will match 1 because 1 == 1.0 is True. This can cause unexpected matches if you are not aware of Python's equality rules. If you need to distinguish between True and 1, you cannot use a literal pattern alone; you would need a guard that checks the type.
OR Pattern Precedence
When combining OR patterns with other patterns, the | operator has lower precedence than sequence or mapping patterns. For example, case [1 | 2] matches a list whose single element is either 1 or 2, not a list that is either [1] or [2]. Parentheses can clarify intent:
case ([1] | [2]): # matches [1] or [2]
Floating-Point Precision
As mentioned, floating-point literals can behave unexpectedly due to binary representation. case 0.1 will not match 0.1 + 1e-16 even if they are mathematically close. Avoid using float literals in patterns unless you are matching exact values that are known to be represented precisely, such as integers stored as floats.
Performance and Maintainability Considerations
Using a match statement with literal patterns is generally as fast as an equivalent if-elif chain, because Python compiles the match into a series of comparisons. The main performance difference comes from the complexity of the patterns. For simple literal patterns, the overhead is negligible. However, if you have many cases, the match statement may be more readable and easier to maintain than a long if-elif chain, especially when combined with other pattern types.
From a maintainability perspective, literal patterns make the set of accepted values explicit. This is useful for protocol handling, command parsing, or state machines. When the list of values changes, you can see all cases in one place. The downside is that adding a new literal requires editing the match statement, which is the same as editing an if chain. For large sets of values, a dictionary mapping values to functions might be more flexible, but it loses the ability to use guards and nested patterns.
Consider the following tradeoff:
- Use
matchwith literal patterns when the logic is straightforward and you want to combine exact matches with guards or structural patterns. - Use a dictionary lookup when you only need to map values to actions and do not need guards or nested matching.
- Use
if-elifwhen the conditions are not simple equality checks, such as range comparisons or type checks.
In practice, the match statement's literal patterns shine when you need to handle a mixture of exact values and more complex patterns in a single dispatch. The readability gain often outweighs any micro-performance differences, which are unlikely to be the bottleneck in real applications.
When to Use Dotted Names for Constants
If you want to match against a constant defined elsewhere, you cannot use a bare name because it becomes a capture pattern. Instead, use a dotted name, such as an enum member or a class attribute. For example:
from enum import Enum class Color(Enum): RED = 1 GREEN = 2 BLUE = 3 def describe(color): match color: case Color.RED: return "red" case Color.GREEN: return "green" case Color.BLUE: return "blue" case _: return "unknown"
Dotted names are treated as literal values, not capture patterns. This is the recommended way to match against named constants. It keeps the pattern readable and avoids accidental variable shadowing.
Literal patterns are a small but essential part of Python's pattern matching. They provide a concise way to express exact-value dispatch, and when combined with guards, OR patterns, and structural patterns, they become a powerful tool for writing clear, maintainable control flow. Understanding the distinction between literal and capture patterns is critical to avoiding subtle bugs, especially when variables are involved.