Lección 7 Expresiones Regulares
nos introduciremos a las expresiones regulares que nos presenta python
- Expresiones regulares
- Sensibilidad a mayúsculas y minúsculas
- Limpiar la entrada del usuario
- Extraer la entrada del usuario
- Resumiendo
Expresiones regulares
Las expresiones regulares o “regexes” nos permitirán examinar patrones dentro de nuestro código. Por ejemplo, es posible que queramos validar que una dirección de correo electrónico tenga el formato correcto. Las expresiones regulares nos permitirán examinar expresiones de esta manera.
Para comenzar, escriba
code validate.py
otouch validate.py
en la ventana de terminal. Luego, codifique de la siguiente manera en el editor de texto:email = input("What's your email? ").strip() if "@" in email: print("Valid") else: print("Invalid")
Tenga en cuenta que
strip
eliminará los espacios en blanco al principio o al final de la entrada. Al ejecutar este programa, verá que siempre que@
se ingrese un símbolo, el programa considerará la entrada como válida.Sin embargo, se puede imaginar que uno podría ingresar
@@
solo y que la entrada podría considerarse válida. Podríamos considerar que una dirección de correo electrónico tiene al menos uno@
y un.
lugar dentro de ella. Modifique su código de la siguiente manera:email = input("What's your email? ").strip() if "@" in email and "." in email: print("Valid") else: print("Invalid")
Tenga en cuenta que, si bien esto funciona como se esperaba, nuestro usuario podría ser adversario, escribir simplemente
@.
haría que el programa regresaravalid
.Podemos mejorar la lógica de nuestro programa de la siguiente manera:
email = input("What's your email? ").strip() username, domain = email.split("@") if username and "." in domain: print("Valid") else: print("Invalid")
Observe cómo
strip
se utiliza el método para determinar siusername
existe y si.
está dentro de ladomain
variable. Al ejecutar este programa, se podría considerar una dirección de correo electrónico estándar ingresada por ustedvalid
. Al escribirmalan@harvard
solo, verá que el programa considera esta entrada comoinvalid
.Podemos ser aún más precisos, modificando nuestro código de la siguiente manera:
email = input("What's your email? ").strip() username, domain = email.split("@") if username and domain.endswith(".edu"): print("Valid") else: print("Invalid")
Observe cómo el
endswith
método comprobará sidomain
contiene.edu
. Sin embargo, un usuario nefasto aún podría descifrar nuestro código. Por ejemplo, un usuario podría escribirmalan@.edu
y se consideraría válido.De hecho, podríamos seguir repitiendo este código nosotros mismos. Sin embargo, resulta que Python tiene una biblioteca existente llamada
re
que tiene una serie de funciones integradas que pueden validar las entradas del usuario frente a patrones.Una de las funciones más versátiles dentro de la biblioteca
re
essearch
.La
search
biblioteca sigue la firmare.search(pattern, string, flags=0)
. Siguiendo esta firma, podemos modificar nuestro código de la siguiente manera:import re email = input("What's your email? ").strip() if re.search("@", email): print("Valid") else: print("Invalid")
Tenga en cuenta que esto no aumenta la funcionalidad de nuestro programa en absoluto. De hecho, es un paso atrás.
Podemos mejorar la funcionalidad de nuestro programa. Sin embargo, necesitamos avanzar en nuestro vocabulario
validation
. Resulta que en el mundo de las expresiones regulares existen ciertos símbolos que nos permiten identificar patrones. En este punto, solo hemos estado comprobando fragmentos de texto específicos como@
. Sucede que se pueden pasar muchos símbolos especiales al compilador con el fin de realizar la validación. Una lista no exhaustiva de esos patrones es la siguiente:. any character except a new line * 0 or more repetitions + 1 or more repetitions ? 0 or 1 repetition {m} m repetitions {m,n} m-n repetitions
Implementando esto dentro de nuestro código, modifica el tuyo de la siguiente manera:
import re email = input("What's your email? ").strip() if re.search(".+@.+", email): print("Valid") else: print("Invalid")
Tenga en cuenta que no nos importa cuál sea el nombre de usuario o el dominio. Lo que nos importa es el patrón.
.+
se utiliza para determinar si hay algo a la izquierda de la dirección de correo electrónico y si hay algo a la derecha de la dirección de correo electrónico. Al ejecutar su código, escribirmalan@
, notará que la entrada se considera comoinvalid
esperábamos.Si hubiéramos usado una expresión regular
.*@.*
en nuestro código anterior, puede visualizar esto de la siguiente manera:Observe la representación de
state machine
nuestra expresión regular. A la izquierda, el compilador comienza a evaluar la declaración de izquierda a derecha. Una vez que llegamosq1
a la pregunta 1, el compilador lee una y otra vez según la expresión que se le entrega. Luego, se cambia el estado mirando ahoraq2
o se valida la segunda pregunta. Nuevamente, la flecha indica cómo se evaluará la expresión una y otra vez según nuestra programación. Luego, como lo muestra el doble círculo, se alcanza el estado final de la máquina de estados.Teniendo en cuenta la expresión regular que utilizamos en nuestro código,
.+@.+
puede visualizarla de la siguiente manera:Observe cómo
q1
es cualquier carácter proporcionado por el usuario, incluido 'q2' como 1 o más repeticiones de caracteres. A esto le sigue el símbolo '@'. Luego,q3
busca cualquier carácter proporcionado por el usuario, incluidasq4
1 o más repeticiones de caracteres.Las
re
funcionesre.search
y y otras similares buscan patrones.Continuando con nuestra mejora de este código, podríamos mejorar nuestro código de la siguiente manera:
import re email = input("What's your email? ").strip() if re.search(".+@.+.edu", email): print("Valid") else: print("Invalid")
Tenga en cuenta, sin embargo, que se podría escribir
malan@harvard?edu
y se podría considerar válido. ¿Por qué es este el caso? Quizás reconozcas que en el lenguaje de validación, a.
significa cualquier carácter.Podemos modificar nuestro código de la siguiente manera:
import re email = input("What's your email? ").strip() if re.search(r".+@.+\.edu", email): print("Valid") else: print("Invalid")
Observe cómo utilizamos el "carácter de escape" o
\
como una forma de considerarlo.
como parte de nuestra cadena en lugar de nuestra expresión de validación. Al probar su código, notará quemalan@harvard.edu
se considera válido y quemalan@harvard?edu
no es válido.Ahora que utilizamos caracteres de escape, es un buen momento para introducir "cadenas sin formato". En Python, las cadenas sin formato son cadenas que no dan formato a caracteres especiales; en cambio, cada carácter se toma por su valor nominal. Imagínese
\n
, por ejemplo. Hemos visto en una conferencia anterior cómo, en una cadena normal, estos dos caracteres se convierten en uno: un carácter de nueva línea especial. En una cadena sin formato, sin embargo,\n
no se trata como\n
el carácter especial, sino como un carácter único\
y únicon
. Colocar unr
delante de una cadena le dice al intérprete de Python que trate la cadena como una cadena sin formato, similar a cómo colocar unf
delante de una cadena le dice al intérprete de Python que trate la cadena como una cadena de formato:import re email = input("What's your email? ").strip() if re.search(r"^.+@.+\.edu$", email): print("Valid") else: print("Invalid")
Ahora nos hemos asegurado de que el intérprete de Python no lo trate
\.
como un carácter especial. En lugar de eso, simplemente como\
seguido de.
—lo cual, en términos de expresiones regulares, significa coincidir con un “.” literal.¡Aún puedes imaginar cómo nuestros usuarios podrían crearnos problemas! Por ejemplo, podría escribir una oración como
My email address is malan@harvard.edu.
y toda la oración se considerará válida. Podemos ser aún más precisos en nuestra codificación.Da la casualidad de que tenemos más símbolos especiales a nuestra disposición en la validación:
^ matches the start of the string $ matches the end of the string or just before the newline at the end of the string
Podemos modificar nuestro código usando nuestro vocabulario agregado de la siguiente manera:
import re email = input("What's your email? ").strip() if re.search(r"^.+@.+\.edu$", email): print("Valid") else: print("Invalid")
Observe que esto tiene el efecto de buscar este patrón exacto que coincida con el inicio y el final de la expresión que se está validando. Escribir una frase como
My email address is malan@harvard.edu.
ahora se considera inválido.¡Proponemos que podemos hacerlo aún mejor! Aunque ahora estemos buscando el nombre de usuario al principio de la cadena, el
@
símbolo y el nombre de dominio al final, ¡podemos escribir tantos@
símbolos como queramos!malan@@@harvard.edu
se considera válido!Podemos agregar a nuestro vocabulario lo siguiente:
[] set of characters [^] complementing the set
Usando estas nuevas habilidades, podemos modificar nuestra expresión de la siguiente manera:
import re email = input("What's your email? ").strip() if re.search(r"^[^@]+@[^@]+\.edu$", email): print("Valid") else: print("Invalid")
Observe que eso
^
significa hacer coincidir al comienzo de la cadena. Completamente al final de nuestra expresión,$
significa coincidir al final de la cadena.[^@]+
significa cualquier carácter excepto un@
. Entonces, tenemos un literal@
.[^@]+\.edu
significa cualquier carácter excepto@
seguido de una expresión que termine en.edu
. Escribirmalan@@@harvard.edu
ahora se considera no válido.Aún podemos mejorar aún más esta expresión regular. ¡Resulta que existen ciertos requisitos sobre lo que puede ser una dirección de correo electrónico! Actualmente, nuestra expresión de validación es demasiado complaciente. Es posible que solo queramos permitir caracteres que se usan normalmente en una oración. Podemos modificar nuestro código de la siguiente manera:
import re email = input("What's your email? ").strip() if re.search(r"^[a-zA-Z0-9_]+@[a-zA-Z0-9_]+\.edu$", email): print("Valid") else: print("Invalid")
Observe que
[a-zA-Z0-9_]
le indica a la validación que los caracteres deben estar entrea
yz
, entreA
yZ
, entre0
y9
y potencialmente incluir un_
símbolo. Al probar la entrada, encontrará que se pueden indicar muchos errores potenciales del usuario.Afortunadamente, los programadores que trabajan duro han incorporado patrones comunes en expresiones regulares. En este caso, puede modificar su código de la siguiente manera:
import re email = input("What's your email? ").strip() if re.search(r"^\w+@\w+\.edu$", email): print("Valid") else: print("Invalid")
Observe que
\w
es lo mismo que[a-zA-Z0-9_]
. ¡Gracias, programadores trabajadores!Aquí hay algunos patrones adicionales que podemos agregar a nuestro vocabulario:
\d decimal digit \D not a decimal digit \s whitespace characters \S not a whitespace character \w word character, as well as numbers and the underscore \W not a word character
Ahora sabemos que no existen simplemente
.edu
direcciones de correo electrónico. Podríamos modificar nuestro código de la siguiente manera:import re email = input("What's your email? ").strip() if re.search(r"^\w+@\w.+\.(com|edu|gov|net|org)$", email): print("Valid") else: print("Invalid")
Observe que
|
tiene el impacto de anor
en nuestra expresión.Agregando aún más símbolos a nuestro vocabulario, aquí hay algunos más para considerar:
A|B either A or B (...) a group (?:...) non-capturing version
Sensibilidad a mayúsculas y minúsculas
Para ilustrar cómo se pueden abordar los problemas relacionados con la distinción entre mayúsculas y minúsculas, donde hay una diferencia entre
EDU
yedu
similares, rebobinemos nuestro código a lo siguiente:import re email = input("What's your email? ").strip() if re.search(r"^\w+@\w+\.edu$", email): print("Valid") else: print("Invalid")
Observe cómo hemos eliminado las
|
declaraciones proporcionadas anteriormente.Recuerde que dentro de la
re.search
función hay un parámetro paraflags
.Algunas variables de bandera integradas son:
re.IGNORECASE re.MULTILINE re.DOTALL
Considere cómo podría usarlos en su código.
Por lo tanto, podemos cambiar nuestro código de la siguiente manera.
import re email = input("What's your email? ").strip() if re.search(r"^\w+@\w+\.edu$", email, re.IGNORECASE): print("Valid") else: print("Invalid")
Observe cómo agregamos un tercer parámetro
re.IGNORECASE
. Al ejecutar este programa conMALAN@HARVARD.EDU
, la entrada ahora se considera válida.Considere la siguiente dirección de correo electrónico
malan@cs50.harvard.edu
. Usando nuestro código anterior, esto se consideraría inválido. ¿Por qué podría ser eso?Como hay un adicional
.
, el programa lo considera no válido.Resulta que podemos, viendo nuestro vocabulario de antes, podemos agrupar ideas.
A|B either A or B (...) a group (?:...) non-caputuring version
Podemos modificar nuestro código de la siguiente manera:
import re email = input("What's your email? ").strip() if re.search(r"^\w+@(\w+\.)?\w+\.edu$", email, re.IGNORECASE): print("Valid") else: print("Invalid")
Observe cómo le
(\w+\.)?
comunica al compilador que esta nueva expresión puede estar ahí una vez o no estar ahí. Por tanto, ambosmalan@cs50.harvard.edu
ymalan@harvard.edu
se consideran válidos.Curiosamente, las ediciones que hemos realizado hasta ahora en nuestro código no abarcan por completo todas las comprobaciones que se podrían realizar para garantizar una dirección de correo electrónico válida. De hecho, aquí está la expresión completa que habría que escribir para asegurarse de que se ingresa un correo electrónico válido:
^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$
Hay otras funciones dentro de la
re
biblioteca que pueden resultarle útiles.re.match
yre.fullmatch
son aquellos que pueden resultarle sumamente útiles.Puede obtener más información en la documentación de Python sobre re .
Limpiar la entrada del usuario
Nunca debe esperar que sus usuarios sigan siempre sus esperanzas de obtener entradas limpias. De hecho, los usuarios a menudo violarán sus intenciones como programador.
Hay formas de limpiar sus datos.
En la ventana de terminal, escriba
code format.py
otouch format.py
. Luego, en el editor de texto, codifique de la siguiente manera:name = input("What's your name? ").strip() print(f"hello, {name}")
Observe que hemos creado, esencialmente, un programa de "hola mundo". Al ejecutar este programa y escribir
David
, ¡funciona bien! Sin embargo, al escribir,Malan, David
observe que el programa no funciona según lo previsto. ¿Cómo podríamos modificar nuestro programa para limpiar esta entrada?Modifique su código de la siguiente manera.
name = input("What's your name? ").strip() if "," in name: last, first = name.split(", ") name = f"{first} {last}" print(f"hello, {name}")
Observe cómo
last, first = name.split(", ")
se ejecuta si hay una,
en el nombre. Luego, el nombre se estandariza como nombre y apellido. Al ejecutar nuestro código y escribirMalan, David
, puede ver cómo este programa limpia al menos un escenario en el que un usuario escribe algo inesperado.Es posible que observe que escribir
Malan,David
sin espacios hace que el compilador genere un error. Como ahora conocemos algo de sintaxis de expresiones regulares, apliquémosla a nuestro código:import re name = input("What's your name? ").strip() matches = re.search(r"^(.+), (.+)$", name) if matches: last, first = matches.groups() name = first + " " + last print(f"hello, {name}")
Tenga en cuenta que
re.search
puede devolver un conjunto de coincidencias extraídas de la entrada del usuario. Si las coincidencias son devueltas porre.search
. Al ejecutar este programa, escribir unDavid Malan
aviso de cómo no se ejecuta laif
condición y se devuelve el nombre. Si ejecuta el programa escribiendoMalan, David
, el nombre también se devuelve correctamente.Da la casualidad de que podemos solicitar que grupos específicos vuelvan a utilizar
matches.group
. Podemos modificar nuestro código de la siguiente manera:import re name = input("What's your name? ").strip() matches = re.search(r"^(.+), (.+)$", name) if matches: name = matches.group(2) + " " + matches.group(1) print(f"hello, {name}")
Observe cómo, en esta implementación,
group
no está en plural (no hays
).Nuestro código se puede ajustar aún más de la siguiente manera:
import re name = input("What's your name? ").strip() matches = re.search(r"^(.+), (.+)$", name) if matches: name = matches.group(2) + " " + matches.group(1) print(f"hello, {name}")
Observe cómo
group(2)
ygroup(1)
se concatenan junto con un espacio. El primer grupo es el que queda de la coma. El segundo grupo es el que está a la derecha de la coma.Reconozca aún que escribir
Malan,David
sin espacios seguirá rompiendo nuestro código. Por tanto, podemos realizar la siguiente modificación:import re name = input("What's your name? ").strip() matches = re.search(r"^(.+), *(.+)$", name) if matches: name = matches.group(2) + " " + matches.group(1) print(f"hello, {name}")
Observe la adición de
*
en nuestra declaración de validación. Este código ahora aceptará y procesará correctamenteMalan,David
. Además, manejará adecuadamente "David, Malanwith many spaces in front of
David".Es muy común utilizarlo
re.search
como lo hicimos en los ejemplos anteriores, dondematches
está en una línea de código después. Sin embargo, podemos combinar estas declaraciones:import re name = input("What's your name? ").strip() if matches := re.search(r"^(.+), *(.+)$", name): name = matches.group(2) + " " + matches.group(1) print(f"hello, {name}")
Observe cómo combinamos dos líneas de nuestro código. El
:=
operador morsa asigna un valor de derecha a izquierda y nos permite hacer una pregunta booleana al mismo tiempo. Gira la cabeza hacia un lado y verás por qué a esto se le llama operador de morsa.Puede obtener más información en la documentación de Python sobre re .
Extraer la entrada del usuario
Hasta ahora, hemos validado la entrada del usuario y limpiado la entrada del usuario.
Ahora, extraigamos información específica de la entrada del usuario. En la ventana de terminal, escriba
code twitter.py
o.touch twitter.py
y codifique de la siguiente manera en la ventana del editor de texto:url = input("URL: ").strip() print(url)
Observe que si escribimos
https://twitter.com/davidjmalan
, muestra exactamente lo que escribió el usuario. Sin embargo, ¿cómo podríamos extraer sólo el nombre de usuario e ignorar el resto de la URL?Puedes imaginar cómo podríamos simplemente deshacernos del comienzo de la URL estándar de Twitter. Podemos intentar esto de la siguiente manera:
url = input("URL: ").strip() username = url.replace("https://twitter.com/", "") print(f"Username: {username}")
Observe cómo el
replace
método nos permite encontrar un elemento y reemplazarlo por otro. En este caso, encontramos parte de la URL y la reemplazamos por nada. Al escribir la URL completahttps://twitter.com/davidjmalan
, el programa genera efectivamente el nombre de usuario. Sin embargo, ¿cuáles son algunas deficiencias de este programa actual?¿Qué pasaría si el usuario simplemente escribiera
twitter.com
en lugar de incluirhttps://
y similares? Puede imaginar muchos escenarios en los que el usuario puede ingresar o dejar de ingresar partes de la URL que crearían resultados extraños en este programa. Para mejorar este programa, podemos codificar de la siguiente manera:url = input("URL: ").strip() username = url.removeprefix("https://twitter.com/") print(f"Username: {username}")
Observe cómo utilizamos el
removeprefix
método. Este método eliminará el comienzo de una cadena.Las expresiones regulares simplemente nos permiten expresar de manera sucinta los patrones y objetivos.
Dentro de la
re
biblioteca, existe un método llamadosub
. Este método nos permite sustituir un patrón por otra cosa.La firma del
sub
método es la siguiente.re.sub(pattern, repl, string, count=0, flags=0)
Observe cómo
pattern
se refiere a la expresión regular que estamos buscando. Luego, hay unarepl
cadena con la que podemos reemplazar el patrón. Finalmente, está elstring
que queremos hacer la sustitución.Implementando este método en nuestro código, podemos modificar nuestro programa de la siguiente manera:
import re url = input("URL: ").strip() username = re.sub(r"https://twitter.com/", "", url) print(f"Username: {username}")
Observe cómo ejecutar este programa e ingresar
https://twitter.com/davidjmalan
produce el resultado correcto. Sin embargo, todavía hay algunos problemas presentes en nuestro código.El protocolo, el subdominio y la posibilidad de que el usuario haya ingresado cualquier parte de la URL después del nombre de usuario son razones por las que este código aún no es ideal. Podemos abordar aún más estas deficiencias de la siguiente manera:
import re url = input("URL: ").strip() username re.sub(r"^(https?://)?(www\.)?twitter\.com/", "", url) print(f"Username: {username}")
Observe cómo
^
se agregó el símbolo de intercalación a la URL. Observe también cómo.
el compilador podría interpretar incorrectamente. Por lo tanto, lo escapamos usando a\
para hacerlo.\.
Con el fin de tolerar tantohttp
comohttps
, agregamos a?
al final dehttps?
, haciendo que seas
opcional. Además, para acomodarlowww
agregamos(www\.)?
a nuestro código. Finalmente, en caso de que el usuario decida omitir el protocolo por completo,http://
ohttps://
se vuelve opcional usando(https?://)
.Aún así, esperamos ciegamente que lo que el usuario ingrese sea una URL que, de hecho, tenga un nombre de usuario.
Utilizando nuestro conocimiento de
re.search
, podemos mejorar aún más nuestro código.import re url = input("URL: ").strip() matches = re.search(r"^https?://(www\.)?twitter\.com/(.+)$", url, re.IGNORECASE) if matches: print(f"Username:", matches.group(2))
Observe cómo buscamos la expresión regular anterior en la cadena proporcionada por el usuario. En particular, capturamos lo que aparece al final de la URL usando
(.+)$
expresiones regulares. Por lo tanto, si el usuario no ingresa una URL sin un nombre de usuario, no se presentará ninguna entrada.Para reforzar aún más nuestro programa, podemos utilizar nuestro
:=
operador de la siguiente manera:import re url = input("URL: ").strip() if matches := re.search(r"^https?://(?:www\.)?twitter\.com/(.+)$", url, re.IGNORECASE): print(f"Username:", matches.group(1))
Observe que le
?:
dice al compilador que no tiene que capturar lo que hay en ese lugar en nuestra expresión regular.Aún así, podemos ser más explícitos para asegurarnos de que el nombre de usuario ingresado sea correcto. Usando la documentación de Twitter, podemos agregar lo siguiente a nuestra expresión regular:
import re url = input("URL: ").strip() if matches := re.search(r"^https?://(?:www\.)?twitter\.com/([a-z0-9_]+)", url, re.IGNORECASE): print(f"Username:", matches.group(1))
Observe que le
[a-z0-9_]+
dice al compilador que solo esperea-z
,0-9
y_
como parte de la expresión regular. Indica+
que estamos esperando uno o más personajes.Puede obtener más información en la documentación de Python sobre re .
Resumiendo
Ahora ha aprendido un lenguaje completamente nuevo de expresiones regulares que se puede utilizar para validar, limpiar y extraer la entrada del usuario.
- Expresiones regulares
- Sensibilidad a mayúsculas y minúsculas
- Limpiar la entrada del usuario
- Extraer la entrada del usuario