3 puntos por GN⁺ 2024-03-21 | 1 comentarios | Compartir por WhatsApp

Entender el comportamiento del carácter "$" en las expresiones regulares de Python

  • Al usar el módulo re de Python, se suele decir que ^ significa "inicio de la cadena" y que $ significa "fin de la cadena".
  • Sin embargo, $ no siempre significa únicamente "fin de la cadena", y su comportamiento puede variar según la plataforma.
  • En Python, cuando el modo multilínea está desactivado, el carácter $ puede coincidir con el final de la cadena o con el carácter de nueva línea antes del final de la cadena.

Diferencia entre coincidir con el final de la cadena y con el carácter de nueva línea

  • Con el modo multilínea desactivado, en Python no basta con usar solo $ para hacer coincidir exclusivamente el final de una cadena sin carácter de nueva línea.
  • Se pueden usar \z y \Z para hacer coincidir el final de la cadena.
  • En Python, al usar re.MULTILINE, $ coincide con el final de la cadena y con el final de cada línea (justo antes del carácter de nueva línea).

Comparación del comportamiento de expresiones regulares en distintas plataformas

  • Una tabla que compara si hay coincidencia del patrón con "cat\n" en varias plataformas muestra que, si se permite coincidir incluyendo el carácter de nueva línea, usar $ en modo multilínea funciona de forma consistente.
  • Si se quiere coincidir sin incluir el carácter de nueva línea, en todas las plataformas excepto Python y ECMAScript se debe usar \z, y en Python y ECMAScript se debe usar respectivamente \Z o $ sin modo multilínea.

Opinión de GN⁺

  • Este artículo puede alertar a los desarrolladores que usan expresiones regulares sobre el comportamiento inesperado del carácter $ en Python.
  • Las expresiones regulares son muy potentes para el procesamiento de cadenas, pero se enfatiza que hay que tener cuidado porque su comportamiento puede variar según la plataforma.
  • Los desarrolladores deben conocer estas diferencias y realizar pruebas adicionales para evitar problemas de compatibilidad al desarrollar aplicaciones multiplataforma.
  • Otras bibliotecas de expresiones regulares que ofrecen funciones similares incluyen java.util.regex de Java y System.Text.RegularExpressions de .NET, y también es necesario entender sus diferencias de comportamiento según cada plataforma.
  • Al introducir nueva sintaxis o comportamientos de expresiones regulares, se deben considerar la compatibilidad con el código existente, el impacto en el rendimiento y la curva de aprendizaje dentro del equipo, y evaluar bien los beneficios y costos de esos cambios.

1 comentarios

 
GN⁺ 2024-03-21
Comentarios en Hacker News
  • Quienes están familiarizados con las expresiones regulares saben que ^ significa "inicio de la cadena" y $ significa "fin de la cadena". Pero personalmente los pienso como "inicio de la línea" y "fin de la línea". En la mayoría de los casos el resultado es el mismo porque el texto normalmente se procesa línea por línea, pero la perspectiva con la que pienso estos operadores no cambia. Probablemente porque conocí las expresiones regulares por primera vez a través de grep y suelo pensar en la entrada principalmente como "líneas".

    • Las expresiones regulares POSIX y las de Python son distintas. En general, hay que consultar la documentación de expresiones regulares de la implementación que se esté usando, porque la sintaxis no es universal.
    • Según el capítulo 9 de POSIX, las expresiones regulares suelen estar relacionadas con el procesamiento de texto y operan sobre cadenas terminadas en NUL, que marca el final de la cadena. Algunas utilidades limitan el procesamiento a una línea por vez. $ puede corresponder al final de la cadena o al final de la línea, según cómo lo defina la utilidad (o el modo). La mayoría de las utilidades comunes (grep, sed, awk, Python, etc.) lo tratan por defecto como fin de línea.
    • No existe una única sintaxis universal de expresiones regulares. Si no sabes qué lenguaje y qué opciones se están usando, no puedes leerlas ni escribirlas de forma confiable.
  • Es una oportunidad perfecta para presentar a Robert Elder. Hace contenido para YouTube y su blog, tiene una serie sobre expresiones regulares y profundiza mucho en las diferencias de comportamiento entre distintas herramientas.

    • Su contenido más reciente también está muy bueno: https://www.youtube.com/watch?v=ys7yUyyQA-Y
    • Tiene mucho contenido que probablemente interesaría a la gente de HN, por ejemplo sobre la realidad y las dificultades de la consultoría.
  • Las expresiones regulares fueron una de las primeras cosas que realmente interioricé cuando aprendí Perl por primera vez. (Perl todavía ocupa un lugar especial en mi corazón gracias al libro "Camel")

    • Hoy en día, lo más importante es saber que las implementaciones son diferentes y adoptar el hábito de consultar la referencia de aquello con lo que estés trabajando.
    • Por ejemplo, las expresiones regulares de Emacs usan \s_- como clase de caracteres en vez de \w (o algo por el estilo que no recuerdo sin consultar la referencia), pero Emacs tiene una documentación excelente y es muy fácil descubrir estas cosas.
    • Algunas utilidades requieren escapar los paréntesis y otras no. A veces ese comportamiento se puede configurar y a veces no.
    • Ya pasé por todas las etapas de confusión, frustración y negación, y ahora simplemente lo acepto. El concepto es el mismo en todas partes, pero el sabor cambia.
  • Ya puedo imaginar a malos gerentes de contratación agregando "¿cómo haces match con el final de una cadena en una expresión regular?" a su lista de preguntas tipo "¡ja! ¡no conocías este truco!".

  • Es raro dejar a Perl fuera de la lista cuando se habla de expresiones regulares.

    • En la documentación perlre, la explicación de $ es: hace match con el final de la cadena (o antes del salto de línea al final de la cadena; o antes de cualquier salto de línea si se usa /m)
  • Raku (antes Perl 6) eligió ^ y $ para representar el inicio y el final de la cadena, e introdujo ^^ y $$ para representar el inicio y el final de la línea. El modo multilínea no está disponible ni es necesario.

    • Una de las ventajas de una reconsideración/reescritura completa es poder aprender del hecho de que el comportamiento anterior sorprendía a la gente.
  • ¿De verdad hay gente que piensa que las expresiones regulares están estandarizadas? Cambiar de contexto siempre implica volver a aprender.

  • Hay una confusión entre cadena y línea. Una cadena es una secuencia de caracteres, y una línea puede ser dos cosas distintas. Si consideras el salto de línea como terminador de línea, una línea es una secuencia de caracteres que no son saltos de línea e incluye el salto de línea. Si no hay salto de línea, no es una línea completa. Así lo usa POSIX. Si consideras el salto de línea como separador de líneas, una línea es una secuencia de caracteres que no son saltos de línea. En cualquiera de los dos casos, el contenido de la línea termina antes del salto de línea, ya sea porque lo termina o porque la separa de la siguiente línea.

    • El significado de ^ y $ se basa en las líneas, independientemente de si se está en modo de una sola línea o multilínea. Para significados basados en cadenas —por ejemplo, cuando al trabajar con archivos piensas en el archivo completo— se usan \A y \Z, o sus equivalentes.
  • Esto ha provocado varios bugs graves en apps basadas en Ruby. Yo siempre uso \A\z.