W3docs

Grupos en Strings de Python — Grupos de Captura en Regex

Aprende cómo funcionan los grupos de captura en Python: group(), groups(), grupos con nombre, no capturantes y referencias inversas con ejemplos.

Cuando el módulo re de Python encuentra una coincidencia dentro de un string, te devuelve un objeto match. El objeto match es más que un simple resultado de sí/no — recuerda cada subpatrón que encerraste entre paréntesis, llamado grupo de captura. Los métodos .group() y .groups() son la forma de recuperar esas partes capturadas.

Este capítulo cubre todo lo que necesitas para usar grupos con confianza: grupos numerados, grupos con nombre, grupos no capturantes, referencias inversas en sustituciones y errores comunes.

¿Qué es un Grupo de Captura?

Un grupo de captura es una parte de un patrón regex encerrada entre paréntesis simples (...). Cuando el patrón coincide, Python guarda el texto que coincidió con cada par de paréntesis por separado, además de guardar la coincidencia completa.

import re

m = re.search(r'(\d{4})-(\d{2})-(\d{2})', '2024-03-15')

print(m.group(0))  # 2024-03-15  — the whole match
print(m.group(1))  # 2024        — first group
print(m.group(2))  # 03          — second group
print(m.group(3))  # 15          — third group

Los grupos se numeran de izquierda a derecha, comenzando en 1, en el orden en que aparece su paréntesis de apertura. group(0) (o simplemente group() sin argumento) siempre devuelve la coincidencia completa.

El Método .group()

.group(n) devuelve el texto que coincidió con el enésimo grupo de captura. Puedes pasar múltiples índices a la vez para obtener una tupla de resultados.

import re

m = re.search(r'(\w+)@(\w+)\.(\w+)', '[email protected]')

# Individual groups
print(m.group(1))       # alice
print(m.group(2))       # example
print(m.group(3))       # com

# Multiple at once
print(m.group(1, 3))    # ('alice', 'com')

Si un grupo existe en el patrón pero no participó en la coincidencia (por ejemplo, estaba dentro de una sección opcional), group(n) devuelve None.

import re

# group(1) is optional — it may not match
m = re.search(r'(\d+)?-(\d+)', 'abc-456')
print(m.group(1))  # None  (the optional \d+ did not match)
print(m.group(2))  # 456

El Método .groups()

.groups() devuelve una tupla que contiene el texto coincidente de cada grupo de captura, en orden. Es un atajo conveniente cuando quieres todos los grupos a la vez.

import re

m = re.search(r'(\d{4})-(\d{2})-(\d{2})', '2024-03-15')
print(m.groups())  # ('2024', '03', '15')

Proporcionar un Valor Predeterminado para Grupos sin Coincidencia

Si un grupo no participó en la coincidencia, aparece como None en la tupla. Puedes suministrar un argumento default para reemplazar esos valores None con algo más útil.

import re

m = re.search(r'(\d+)?-(\d+)', 'abc-456')
print(m.groups())              # (None, '456')
print(m.groups(default='0'))   # ('0', '456')

Grupos con Nombre usando (?P<name>...)

Los grupos numerados se vuelven difíciles de seguir en patrones complejos. Los grupos con nombre te permiten asignar una etiqueta significativa a cada grupo usando la sintaxis (?P<name>...), y luego recuperar el valor por nombre en lugar de por posición.

import re

pattern = r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})'
m = re.search(pattern, '2024-03-15')

print(m.group('year'))   # 2024
print(m.group('month'))  # 03
print(m.group('day'))    # 15

Los grupos con nombre aún tienen un número, por lo que puedes acceder a ellos de cualquier manera.

El Método .groupdict()

Cuando tienes grupos con nombre, .groupdict() devuelve un diccionario que mapea cada nombre con el texto que coincidió. Esto es útil cuando quieres desempaquetar una coincidencia en un objeto estructurado.

import re

m = re.search(
    r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})',
    '2024-03-15'
)
print(m.groupdict())
# {'year': '2024', 'month': '03', 'day': '15'}

Luego puedes usar el resultado directamente:

date_parts = m.groupdict()
print(f"{date_parts['day']}/{date_parts['month']}/{date_parts['year']}")
# 15/03/2024

Grupos No Capturantes con (?:...)

A veces necesitas agrupar parte de un patrón para aplicar un cuantificador o una alternancia, pero no quieres que ese grupo aparezca en los resultados de la coincidencia. Usa (?:...) — un grupo no capturante.

import re

# Without non-capturing group: both parts appear in groups()
m1 = re.search(r'(\d{4})-(\d{2})-(\d{2})', '2024-03-15')
print(m1.groups())  # ('2024', '03', '15')

# Year grouped but not captured — month and day are groups 1 and 2
m2 = re.search(r'(?:\d{4})-(\d{2})-(\d{2})', '2024-03-15')
print(m2.groups())  # ('03', '15')

Los grupos no capturantes son una buena práctica cuando no necesitas una subcoincidencia en particular — mantienen limpios los índices de tus grupos y evitan desperdiciar memoria.

Grupos Anidados

Los grupos pueden anidarse. La numeración siempre se basa en la posición del paréntesis de apertura, contando de izquierda a derecha.

import re

# ((\d{4})-(\d{2}))-(\d{2})
# group 1 = the outer (year-month pair)
# group 2 = year
# group 3 = month
# group 4 = day
m = re.search(r'((\d{4})-(\d{2}))-(\d{2})', '2024-03-15')
print(m.group(1))   # 2024-03
print(m.group(2))   # 2024
print(m.group(3))   # 03
print(m.group(4))   # 15
print(m.groups())   # ('2024-03', '2024', '03', '15')

Grupos con re.findall()

Cuando usas re.findall() y el patrón contiene exactamente un grupo de captura, devuelve una lista de strings — una por coincidencia.

Cuando el patrón contiene dos o más grupos de captura, devuelve una lista de tuplas.

import re

# One group → list of strings
emails = '[email protected], [email protected]'
usernames = re.findall(r'(\w+)@\w+\.\w+', emails)
print(usernames)  # ['alice', 'bob']

# Two groups → list of tuples
pairs = re.findall(r'(\w+)@(\w+)\.com', emails)
print(pairs)  # [('alice', 'gmail'), ('bob', 'yahoo')]

Grupos con re.finditer()

re.finditer() devuelve un iterador de objetos match, por lo que puedes acceder a .group() y .groups() en cada uno individualmente. Este es el enfoque más flexible cuando necesitas todos los detalles de la coincidencia para cada ocurrencia.

import re

text = '[email protected], [email protected]'
for m in re.finditer(r'(?P<user>\w+)@(?P<domain>\w+)\.com', text):
    print(m.group('user'), '->', m.group('domain'))
# alice -> gmail
# bob   -> yahoo

Referencias Inversas en re.sub()

Los grupos de captura también potencian las referencias inversas en re.sub(). En el string de reemplazo, \1, \2, … hacen referencia al texto capturado por el grupo 1, el grupo 2, etc. Los grupos con nombre usan \g<name>.

import re

# Swap first and last name
result = re.sub(r'(\w+)\s(\w+)', r'\2 \1', 'John Smith')
print(result)  # Smith John

# Same using named groups
result2 = re.sub(
    r'(?P<first>\w+)\s(?P<last>\w+)',
    r'\g<last> \g<first>',
    'Jane Doe'
)
print(result2)  # Doe Jane

Posición de un Grupo: .start(), .end(), .span()

Los objetos match exponen la posición de cada grupo, no solo su texto. Pasa un número de grupo (o nombre) a .start(), .end() o .span().

import re

m = re.search(r'(\d{4})-(\d{2})', '2024-03')
print(m.span(1))    # (0, 4)  — year occupies indices 0..3
print(m.start(2))   # 5       — month starts at index 5
print(m.end(2))     # 7       — month ends before index 7

Referencia Rápida

MétodoDevuelveNotas
m.group() o m.group(0)String de la coincidencia completaSiempre disponible
m.group(n)Texto coincidente del grupo nNone si el grupo no coincidió
m.group(n, m, ...)Tupla de gruposMúltiples índices en una sola llamada
m.groups()Tupla de todos los gruposdefault= opcional para los no coincidentes
m.groupdict()Dict de grupos con nombreSolo aparecen los grupos con nombre
m.start(n) / m.end(n)Índice de inicio / fin del grupo nPasa 0 para la coincidencia completa
m.span(n)Tupla (start, end)Atajo para ambos

Errores Comunes

Usar .group() sin verificar si hay None. re.search() devuelve None cuando no hay coincidencia, por lo que llamar a .group() directamente sobre el resultado lanza un AttributeError. Siempre protege la llamada:

import re

m = re.search(r'(\d+)', 'no digits here')
if m:
    print(m.group(1))
else:
    print('no match')
# no digits here

Confundir .group() con .groups(). .group(1) devuelve un string; .groups() siempre devuelve una tupla. Mezclarlos provoca errores de tipo sutiles.

Olvidar que re.findall() cambia de forma según los grupos. Sin grupos devuelve una lista plana de strings; con grupos devuelve una lista de tuplas. Agregar o eliminar un grupo de captura puede romper silenciosamente el código que procesa el resultado.

Cuándo Usar Cada Enfoque

  • Usa grupos numerados para patrones simples y cortos con pocos grupos.
  • Usa grupos con nombre cuando los patrones son complejos, o cuando usarás el resultado a lo largo de varias líneas de código — los nombres actúan como autodocumentación.
  • Usa grupos no capturantes (?:...) cuando necesites agrupar solo para aplicar un cuantificador o una alternancia, no para extraer un valor.
  • Usa .groups() para desempaquetar todas las capturas a la vez en una tupla.
  • Usa .groupdict() cuando todos los grupos tienen nombre y quieres un diccionario.

Para una visión más amplia del módulo re y su sintaxis de patrones, consulta el capítulo Python RegEx. Para operaciones con strings que no requieren regex, consulta Modify Strings y Python String Methods.

Práctica

Práctica
In Python, which functions or methods can be used to define a group within strings?
In Python, which functions or methods can be used to define a group within strings?
Was this page helpful?