# Regular Expression Matching vs. Capturing: How CPython Distinguishes Pattern Verification from Value Extraction

> Understand the difference between regex matching and capturing in Python. Learn how CPython distinguishes pattern verification from value extraction for efficient text processing.

- Repository: [Python/cpython](https://github.com/python/cpython)
- Tags: internals
- Published: 2026-02-13

---

**Regular expression matching determines whether a pattern applies to a string and identifies the full matching span, while capturing uses parentheses to record specific sub-spans within that match for individual retrieval.**

The CPython source code distinguishes between these two operations through a clear architectural separation in the `_sre` module. While the matching engine verifies if compiled bytecode fits the target text, it simultaneously maintains a mark table to track capture group boundaries. Understanding this distinction helps developers write more efficient patterns and correctly interpret match objects returned by the `re` module.

## What Is Regular Expression Matching?

**Regular expression matching** is the process of verifying whether a pattern fits a target string. In CPython, this operation is implemented in the `_sre` C extension, specifically in [`Modules/_sre/sre.c`](https://github.com/python/cpython/blob/main/Modules/_sre/sre.c). When you call `re.match()` or `re.search()`, the engine walks the compiled bytecode (`SRE_CODE`) through functions like `_sre_SRE_Pattern_match_impl` (around line 781) to validate the entire pattern against the input.

The result is a **match object** (`_sre.SRE_Match` in the C implementation) that represents the overall portion of the string satisfying the pattern. This object provides the full span via methods like `m.span()` and the complete matched text via `m.group()`, but it does not automatically expose sub-components unless the pattern explicitly defines capture groups.

## What Are Regex Capture Groups?

**Capture groups** are sub-patterns delimited by parentheses `(...)` inside a regular expression. During the matching process, the engine records the start and end offsets of each group in the match object's internal storage. According to the CPython source in [`Modules/_sre/sre.c`](https://github.com/python/cpython/blob/main/Modules/_sre/sre.c) (lines 950-970), this involves manipulating the `mark` array to store positional boundaries.

These offsets are later exposed to Python through methods such as `Match.group()`, `Match.groups()`, `Match.groupdict()`, and `Match.start()`/`end()`. When the engine encounters an opening parenthesis, it pushes the current text position onto a group stack; upon reaching the matching closing parenthesis, it pops the position and fills the `mark` table (see `state->mark` handling around line 550).

## Technical Differences in CPython's Implementation

The distinction between matching and capturing manifests in three key areas: scope, engine work, and API surface.

**Scope of Operation**

- **Matching** returns a single `Match` object indicating whether the pattern succeeded and provides the full span (`m.span()`).
- **Capturing** returns the same `Match` object but adds stored sub-spans for each parenthesized group, accessible via `m.group(n)` or `m.group('name')`.

**Engine-Level Processing**

- **Matching** walks the compiled bytecode and verifies that the entire pattern fits the text through the main match loop in `_sre_SRE_Pattern_match_impl`.
- **Capturing** performs the same bytecode walk but adds overhead by managing the `mark` array and group stack to record entry and exit positions for every parenthesis pair.

**API Exposure**

- **Matching** functions include `match()`, `search()`, `fullmatch()`, `findall()`, and `finditer()` defined in [`Lib/re/__init__.py`](https://github.com/python/cpython/blob/main/Lib/re/__init__.py).
- **Capturing** retrieval methods belong to the match object itself: `group()`, `groups()`, `groupdict()`, `start()`, `end()`, and `span()`, implemented in `_sre_SRE_Match_group_impl` (lines 2402-2464) which looks up stored offsets in the `mark` table.

## Code Examples: From Pattern Matching to Group Extraction

The following examples demonstrate the progression from simple pattern verification to value extraction using capture groups.

```python
import re

# 1. Simple match – only the whole pattern matters

m = re.match(r'cat', 'catapult')
print(m)                     # <re.Match object; span=(0, 3), match='cat'>

print(m.group())             # cat

print(m.span())              # (0, 3)

```

```python

# 2. Using a capture group to pull out the number

m = re.match(r'cat(\d+)', 'cat123')
print(m)                     # <re.Match object; span=(0, 6), match='cat123'>

print(m.group(1))            # 123

```

```python

# 3. Named capture groups

m = re.search(r'(?P<first>\w+)-(?P<second>\d+)', 'item-42')
print(m.group('first'))      # item

print(m.group('second'))     # 42

print(m.groupdict())         # {'first': 'item', 'second': '42'}

```

```python

# 4. Multiple groups – .groups() returns a tuple of all captured substrings

m = re.search(r'(\w+)-(\d+)-(\w+)', 'a-1-b')
print(m.groups())            # ('a', '1', 'b')

```

Behind the scenes, these calls invoke the C functions `pattern_match` → `_sre_SRE_Pattern_match_impl` in [`Modules/_sre/sre.c`](https://github.com/python/cpython/blob/main/Modules/_sre/sre.c), which builds a `MatchObject`. The `group()` implementation slices substrings using the stored offsets from the `mark` table.

## Summary

- **Regular expression matching** verifies if a pattern fits a string and returns the full match span, implemented in [`Modules/_sre/sre.c`](https://github.com/python/cpython/blob/main/Modules/_sre/sre.c) through bytecode verification.
- **Capture groups** record sub-spans via parentheses, storing offsets in the `mark` array during the same matching pass.
- The match object exposes full matches through `group()` and `span()`, while captured values require `group(n)`, `groups()`, or `groupdict()`.
- CPython's architecture separates the engine (handling `SRE_CODE` and `state->mark`) from the Python interface ([`Lib/re/__init__.py`](https://github.com/python/cpython/blob/main/Lib/re/__init__.py)) to provide both capabilities efficiently.

## Frequently Asked Questions

### Does every regex match operation create capture groups?

No. Capture groups only exist when you explicitly include parentheses `(...)` in your pattern. A pattern without parentheses performs matching only, returning a match object that contains the full span but no sub-group entries in the internal `mark` table. The overhead of managing the group stack and `mark` array is avoided when no capturing parentheses are present.

### How does CPython store capture group positions during matching?

CPython stores capture group positions in the `mark` array within the match state structure. When the engine enters a group (opening parenthesis), it pushes the current text position onto a stack; upon exiting (closing parenthesis), it records both start and end positions in `state->mark`. The implementation in [`Modules/_sre/sre.c`](https://github.com/python/cpython/blob/main/Modules/_sre/sre.c) (around lines 950-970) handles this bookkeeping during the bytecode execution loop.

### Can I use capture groups without affecting the overall match result?

Yes. Capture groups do not change whether a pattern matches; they only record positions if the match succeeds. Non-capturing groups `(?:...)` allow you to group sub-patterns for quantification or alternation without populating the `mark` table, avoiding the storage overhead while maintaining the logical grouping needed for the match logic.

### What is the performance cost of using capture groups?

Capture groups add minor overhead due to the additional bookkeeping required to maintain the `mark` array and group stack during the bytecode walk in [`Modules/_sre/sre.c`](https://github.com/python/cpython/blob/main/Modules/_sre/sre.c). For performance-critical code with many groups, consider using non-capturing groups `(?:...)` where possible, or the `re` module's optimization flags, to reduce the work performed by `_sre_SRE_Pattern_match_impl` during the matching pass.