2.35. Conceptos básicos de patrones

Una expresión regular es un pequeño lenguaje para describir cadenas por su forma en lugar de por su contenido exacto. Úsala cuando una cadena coincida si y solo si sigue un patrón que puedes describir pero no enumerar: «una secuencia de dígitos seguida de una unidad», «una línea que empieza por ERROR y termina en un número», «cualquiera de estas extensiones de archivo, en cualquier orden, con prefijos v opcionales».

Recurre a re solo cuando un método de cadena simple no sea suficiente.

Cada uno de ellos es más rápido, más fácil de leer y más difícil de equivocar que la expresión regular equivalente. Usa expresiones regulares cuando importe la forma de la cadena y no la subcadena exacta.

2.35.1. Las cuatro cosas que usarás

El módulo re de MicroPython expone cuatro cosas:

  • re.compile() – convertir una cadena de patrón en un objeto de patrón compilado que puedes reutilizar.

  • re.match() – probar el patrón al inicio de una cadena. El patrón queda anclado en la posición 0.

  • re.search() – probar el patrón en cualquier parte de una cadena. Devuelve la primera coincidencia.

  • re.sub() – encontrar cada coincidencia y reemplazarla.

Omisiones notables frente a CPython: no hay re.findall, ni re.finditer, ni re.split a nivel de módulo (los patrones compilados tienen en su lugar un método split), ni re.fullmatch, ni constantes de banderas como re.IGNORECASE. Donde recurrirías a una de ellas en CPython, construye el equivalente a partir de re.search() en un bucle.

2.35.2. Un primer patrón

El patrón r'\d+' coincide con uno o más dígitos:

>>> import re
>>> m = re.search(r'\d+', 'sensor reading 42 ok')
>>> m.group(0)
'42'

Algunas cosas que conviene notar:

  • El patrón se escribe como una cadena en bruto (r'...') para que la barra invertida de \d llegue a re en lugar de procesarse como un escape de cadena de Python. Usa siempre cadenas en bruto para los patrones de expresiones regulares.

  • re.search() devuelve un objeto de coincidencia en caso de éxito y None en caso de fallo. Comprueba siempre antes de llamar a match.group().

  • m.group(0) es el texto completo con el que coincidió el patrón. Los grupos 1, 2, … aparecen más adelante, una vez que el patrón contiene paréntesis de captura.

El mismo patrón con re.match() devuelve None porque la cadena no empieza por un dígito:

>>> re.match(r'\d+', 'sensor reading 42 ok') is None
True
>>> re.match(r'\d+', '42 readings')
<match num=1>

2.35.3. Las piezas de un patrón

La mayoría de los patrones útiles se construyen a partir de un pequeño conjunto de piezas. Las que funcionan en MicroPython:

Caracteres literales – cualquier carácter que no sea especial coincide consigo mismo. hello coincide con hello.

Caracteres especiales. ^ $ * + ? { } [ ] \ | ( ) tienen todos los significados que se indican a continuación. Para que coincida con uno de ellos de forma literal, escápalo con una barra invertida: \. coincide con un punto literal.

Clases de caracteres – abreviaturas para conjuntos de caracteres comunes:

  • \d – cualquier dígito 0-9

  • \D – cualquier carácter que no sea un dígito

  • \s – cualquier carácter de espacio en blanco (espacio, tabulación, salto de línea)

  • \S – cualquier carácter que no sea espacio en blanco

  • \w – cualquier carácter de «palabra»: letras, dígitos, guion bajo

  • \W – cualquier carácter que no sea de palabra

  • . – cualquier carácter excepto el salto de línea

Cuantificadores – cuántas veces debe coincidir la pieza anterior:

  • * – cero o más (codicioso)

  • + – uno o más (codicioso)

  • ? – cero o uno

  • {n} – exactamente n

  • {m,n} – entre m y n (ambos incluidos)

Combinando: \d{3}-\d{4} coincide con tres dígitos, un guion y cuatro dígitos. \s+ coincide con uno o más caracteres de espacio en blanco. hello.*world coincide con hello, cualquier cosa (incluida nada) y luego world.

Nota

Codicioso significa que el cuantificador consume tanto de la entrada como puede mientras sigue permitiendo que el resto del patrón coincida. Frente a hello x world y world, el .* de hello.*world coincide con la secuencia más larga que aún deja un world al final: captura x world y, no el más corto x. Lo mismo ocurre con + y con la forma de rango {m,n}: el motor toma la coincidencia más larga que puede y solo retrocede si el resto del patrón falla.

2.35.4. Sustitución

re.sub() encuentra cada coincidencia y la reemplaza por una cadena. El reemplazo puede hacer referencia a los grupos capturados mediante \1, \2, … (se trata más adelante junto con el resto de la sintaxis de grupos). Sin grupos, re.sub es una búsqueda y reemplazo directos sobre una expresión regular:

>>> re.sub(r'\s+', ' ', 'too    many   spaces')
'too many spaces'

>>> re.sub(r'\d+', 'N', 'log 12, log 345, log 6')
'log N, log N, log N'

El tercer argumento es la cadena sobre la que operar; el resultado es una nueva cadena con cada coincidencia reemplazada.

2.35.5. División – solo sobre un patrón compilado

No existe re.split a nivel de módulo. Para dividir mediante una expresión regular, compila primero el patrón y llama a su método split:

>>> sep = re.compile(r'\s*,\s*')
>>> sep.split('a , b,c ,  d')
['a', 'b', 'c', 'd']

El segundo argumento opcional limita el número de divisiones:

>>> sep.split('a, b, c, d', 2)
['a', 'b', 'c, d']

2.35.6. Compilar para reutilizar

Si el mismo patrón se ejecuta muchas veces, dentro de un bucle o en una función crítica, compílalo una vez y reutiliza el objeto compilado:

digit_run = re.compile(r'\d+')

def first_number(line):
    m = digit_run.search(line)
    return int(m.group(0)) if m else None

Llamar a pattern.match() y pattern.search() sobre un objeto compilado es lo mismo que las funciones a nivel de módulo, pero evita el coste de recompilar en cada llamada.

2.35.7. Patrones que no coinciden con nada

Tres patrones en particular pillan desprevenidos a los desarrolladores:

  • .* coincide con la cadena vacía. re.search(r'.*', s).group(0) devuelve '' con cualquier entrada.

  • Un patrón con un carácter especial sin escapar es un error de sintaxis. re.compile(r'cost: $5') provoca un ValueError porque $ significa «final de cadena». Usa r'cost: \$5'.

  • El punto . no coincide con un salto de línea. Para coincidir a través de saltos de línea, escribe el patrón para gestionarlos explícitamente con [\s\S] o procesa una línea cada vez.

Con estas piezas un patrón puede coincidir con casi cualquier fragmento de texto de forma fija. Extraer datos estructurados de la coincidencia requiere grupos de captura.