Regex (regular expressions)
Información extraida de Automate the boring stuff de Al Sweigart.
Web importante para testear regex: https://regex101.com/
Intro
Ya hemos trabajado con archivos de texto tratando de encontrar, modificar o extraer datos del mismo. Sabemos que teníamos que seguir los siguientes pasos:
- Abrir el archivo en modo lectura
- Recorrer todas las líneas del archivo
- Si la linea empieza por x hacer lo siguiente:
- Buscar la posición del caracter donde comienza el texto que queremos extraer.
- Trocear la cadena de texto usando la posición anterior como punto de partida y almacenarla en una variable o añadirla a una lista.
- Si la linea empieza por x hacer lo siguiente:
El programa quedó así:
Trabajar con regex nos va a ahorrar mucho trabajo. Simplemente definiremos un patrón y con una sola línea de código nos lo buscará y extraerá del texto objeto de trabajo. Los pasos necesarios serán:
- Importar la librería re (
import re
) - Abrir el archivo en modo lectura.
- Encontrar el patrón que queremos buscar y almacenar todas las ocurrencias en una lista.
¿Has visto? No se requiere ningún bucle. El anterior script quedaría así:
Clases de caracter
Shorthand character class | Represents |
---|---|
\d | Cualquier dígito numérico de 0 a 9. |
\D | Cualquier caracter que NO sea un dígito numérico de 0 a 9. |
\w | Cualquier letra, dígito numérico, o el guión bajo. (Piensa en este atajo como el caracter de palabra.) |
\W | Cualquier caracter que NO sea una letra, dígito numérico, o el guión bajo. . |
\s | Cualquier espacio, tabulador, o caracter de nueva línea. (Think of this as matching “space” characters.) |
\S | Cualquier caracter que NO sea espacio, tabulador, o caracter de nueva línea. |
Recetas
Imagina que quieres encontrar un número de teléfono en una cadena. Sabes el patrón: tres números, un guión, tres números, un guión y cuatro números. Aquí tienes un ejemplo: '415-555-4242'
El caracter que usamos en las regex para encontrar números es '\d'. Pues bien, el patrón que queremos encontrar sería: r'\d\d\d-\d\d\d-\d\d\d'.
¿Qué significa la 'r' en r'\d\d\d-\d\d\d-\d\d\d\d'?
r significa 'raw', es decir, crudo. Cogerá la cadena de texto tal cual, sin tomar el caracter contrabarra '\' como caracter de escape.
Pues bien, para extraer el número quedaría:
La salida sería una lista con un elemento = ['415-555-4242']
Imagina que quieres sacar sólo el segundo elemento, es decir, los segundos tres números números. Agruparemos los grupos que queramos hacer usando paréntesis.
Más recetas
Usando re.compile y accediendo a los datos con la función group():
Especificando caracteres opcionales '()?':
Encontrando cero o más '*':
>>> batRegex = re.compile(r'Bat(wo)*man')
>>> mo1 = batRegex.search('The Adventures of Batman')
>>> mo1.group()
'Batman'
>>> mo2 = batRegex.search('The Adventures of Batwoman')
>>> mo2.group()
'Batwoman'
>>> mo3 = batRegex.search('The Adventures of Batwowowowoman')
>>> mo3.group()
'Batwowowowoman'
Encontrando uno o más con '+':
>>> batRegex = re.compile(r'Bat(wo)+man')
>>> mo1 = batRegex.search('The Adventures of Batwoman')
>>> mo1.group()
'Batwoman'
>>> mo2 = batRegex.search('The Adventures of Batwowowowoman')
>>> mo2.group()
'Batwowowowoman'
>>> mo3 = batRegex.search('The Adventures of Batman')
>>> mo3 == None
True
Encontrando repeticiones con las llaves '{}':
Greedy and Nongreedy Matching
Las expresiones de python son greedy por defecto
¡Atención!
El símbolo '?' tiene dos usos en las expresiones regulares: declarar nongreedy matchings (patrones no codiciosos) o marcar un grupo opcional.
Utilizando atajos de caracter
>>> xmasRegex = re.compile(r'\d+\s\w+')
>>> xmasRegex.findall('12 drummers, 11 pipers, 10 lords, 9 ladies, 8 maids, 7
swans, 6 geese, 5 rings, 4 birds, 3 hens, 2 doves, 1 partridge')
['12 drummers', '11 pipers', '10 lords', '9 ladies', '8 maids', '7 swans', '6
geese', '5 rings', '4 birds', '3 hens', '2 doves', '1 partridge']
Compila tus propias clases de caracteres
Inicio y final de cadena
The Wildcard Character
Encontrando todo con el punto '.'
Encontrando nuevas líneas con el '.'
>>> noNewlineRegex = re.compile('.*')
>>> noNewlineRegex.search('Serve the public trust.\nProtect the innocent.
\nUphold the law.').group()
'Serve the public trust.'
>>> newlineRegex = re.compile('.*', re.DOTALL)
>>> newlineRegex.search('Serve the public trust.\nProtect the innocent.
\nUphold the law.').group()
'Serve the public trust.\nProtect the innocent.\nUphold the law.'
Hacerlas insensibles a mayúsculas y minúsculas
>>> robocop = re.compile(r'robocop', re.I)
>>> robocop.search('Robocop is part man, part machine, all cop.').group()
'Robocop'
>>> robocop.search('ROBOCOP protects the innocent.').group()
'ROBOCOP'
>>> robocop.search('Al, why does your programming book talk about robocop so much?').group()
'robocop'
Sustituir cadenas con sub()
Manejando expresiones complejas
Combining re.IGNORECASE, re.DOTALL, and re.VERBOSE
>>> someRegexValue = re.compile('foo', re.IGNORECASE | re.DOTALL | re.VERBOSE)
Repaso
Este capítulo cubrió mucha notación, así que aquí hay una revisión rápida de lo que aprendiste:
- El '?' coincide con cero o uno del grupo anterior.
- El '*' coincide con cero o más del grupo anterior.
- El '+' coincide con uno o más del grupo anterior.
- La {n} coincide exactamente con n del grupo anterior.
- La {n,} coincide con n o más del grupo anterior.
- El {, m} coincide con 0 a m del grupo anterior.
- La {n, m} coincide con al menos n y como máximo m del grupo anterior.
- {n, m}? o *? o +? realiza un partido no 'codicioso' del grupo anterior (non greedy matching).
- '^spam' significa que la cadena debe comenzar con spam.
- 'spam$' significa que la cadena debe terminar con spam.
- Los '.' coincide con cualquier carácter, excepto los caracteres de nueva línea.
- \d, \w y \s coinciden con un dígito, palabra o carácter de espacio, respectivamente.
- \D, \W y \S coinciden con cualquier cosa excepto un dígito, palabra o espacio, respectivamente.
- [abc] coincide con cualquier carácter entre los paréntesis (como a, b, o c).
- abc coincide con cualquier carácter que no esté entre los paréntesis.