2009-12-02 19 views
48

Perl y algunos otros motores regex actuales admiten propiedades Unicode, como la categoría, en una expresión regular. P.ej. en Perl puede usar \p{Ll} para que coincida con una letra minúscula arbitraria, o p{Zs} para cualquier separador de espacio. No veo soporte para esto en las líneas 2.x ni 3.x de Python (con los debidos remordimientos). ¿Alguien sabe de una buena estrategia para obtener un efecto similar? Las soluciones de cosecha propia son bienvenidas.Python regex matching propiedades Unicode

+10

En realidad, Perl admite ** todas las ** propiedades Unicode, no solo las categorías generales. Los ejemplos incluyen '\ p {Block = Greek}, \ p {Script = Armenian}, \ p {General_Category = Uppercase_Letter}, \ p {White_Space}, \ p {Alphabetic}, \ p {Math}, \ p {Bidi_Class = Right_to_Left}, \ p {Word_Break = A_Letter }, \ p {Numeric_Value = 10}, \ p {Hangul_Syllable_Type = Leading_Jamo}, \ p {Sentence_Break = SContinue}, 'y alrededor de 1,000 más. Solo las expresiones regulares de Perl e ICU se molestan en cubrir el complemento completo de las propiedades Unicode. Todos los demás cubren unos pocos, generalmente ni siquiera lo suficiente para un mínimo de trabajo Unicode. – tchrist

Respuesta

22

¿Has probado Ponyguruma, un enlace de Python al motor de expresión regular Oniguruma? En ese motor, simplemente puede decir \p{Armenian} para que coincida con los caracteres armenios. \p{Ll} o \p{Zs} funcionan también.

+2

este módulo no tiene la misma API que Python re module –

+7

El último compromiso con el módulo Ponyguruma fue aparentemente 2010 (http://dev.pocoo.org/hg/sandbox/ponyguruma) mientras que el módulo de expresión regular de Python en PyPI se desarrolla activamente: http://pypi.python.org/pypi/regex – RichVel

4

Tiene razón en que las clases de propiedad Unicode no son compatibles con el analizador de expresiones regex de Python.

Si quisiera hacer un buen hack, que en general sería útil, podría crear un preprocesador que explore una cadena para tales tokens de clase (\p{M} o lo que sea) y los reemplace con los juegos de caracteres correspondientes, de modo que, para ejemplo, \p{M} se convertiría en [\u0300–\u036F\u1DC0–\u1DFF\u20D0–\u20FF\uFE20–\uFE2F], y \P{M} se convertiría en [^\u0300–\u036F\u1DC0–\u1DFF\u20D0–\u20FF\uFE20–\uFE2F].

La gente te lo agradecería. :)

+1

Correcto, la creación de clases de caracteres cruzó por mi mente. Pero con aproximadamente 40 categorías terminas produciendo 80 clases, y eso sin contar los scripts, bloques, planos y demás.Puede valer la pena un pequeño proyecto de código abierto, pero sigue siendo una pesadilla de mantenimiento. Acabo de descubrir que re.VERBOSE no se aplica a las clases de caracteres, por lo que no hay comentarios aquí o espacio en blanco para facilitar la lectura ... – ThomasH

2

Tenga en cuenta que mientras \p{Ll} no tiene equivalente en las expresiones regulares de Python, \p{Zs} debe estar cubierto por '(?u)\s'. El (?u), como dice el documento, "Hacer \ w, \ W, \ b, \ B, \ d, \ D, \ s y \ S depende de la base de datos de propiedades de caracteres Unicode." Y \s significa cualquier carácter de espaciado.

+0

Tienes razón. El problema es que '(? U) \ s' es más grande que '\ p {Zs}', como p. nueva línea. Entonces, si realmente quieres unir separadores de espacio, el primero está sobregenerando. – ThomasH

+3

@ThomasH: para obtener "espacio, excepto no nueva línea", puede utilizar la clase de caracteres doblemente negada: '(? U) [^ \ S \ n]' – bukzor

6

Puede utilizar cuidadosamente unicodedata en cada personaje:

import unicodedata 

def strip_accents(x): 
    return u''.join(c for c in unicodedata.normalize('NFD', x) if unicodedata.category(c) != 'Mn') 
+0

Gracias. Aunque fuera de expresiones regulares, esta podría ser una alternativa viable para ciertos casos. – ThomasH

+0

Parece que el módulo 'Unicodedata' de Python actualmente no contiene información sobre, p. Ej. el script o bloque Unicode de un personaje. Consulte también https://stackoverflow.com/questions/48058402/unicode-table-information-about-a-character-in-python/48060112#48060112 – tripleee

52

El módulo regex (una alternativa al módulo estándar re) admite propiedades codepoint Unicode con la sintaxis \p{}.

+1

No estoy seguro de lo completo que es el soporte '' \ p {} '', pero este módulo se desarrolla activamente y eventualmente debería reemplazar el módulo '' re'' incorporado: vea http://pypi.python.org/pypi/regex – RichVel

+4

+1: 'regex' es un reemplazo directo para stdlib's 're' módulo. Si sabes cómo usar 're'; inmediatamente puede usar 'regex'. 'importge regex as re' y tienes soporte de sintaxis' \ p {} '. Aquí hay un [ejemplo de cómo eliminar todas las puntuaciones en una cadena usando '\ p {P}'] (http://stackoverflow.com/a/11066687) – jfs

5

Hablando de soluciones propias, hace algún tiempo escribí un pequeño program de hacer precisamente eso - convierte una categoría Unicode escrita como \p{...} en un rango de valores, extraído de la Unicode specification (v.5.0.0). Solo se admiten categorías (por ej .: L, Zs) y está restringido al BMP. Lo estoy publicando aquí en caso de que alguien lo encuentre útil (aunque ese Oniguruma realmente parece una mejor opción).

Ejemplo de uso:

>>> from unicode_hack import regex 
>>> pattern = regex(r'^\\p{Lu}(\\p{L}|\\p{N}|_)*') 
>>> print pattern.match(u'疂_1+2').group(0) 
疂_1 
>>> 

Aquí está la source. También hay un JavaScript version, utilizando los mismos datos.

+1

Bueno, aunque estés usando literales hechos a mano para los rangos en el código. Sería bueno tener esos literales generados a partir de alguna forma textual de la especificación. O desde unicodedata (http://docs.python.org/library/unicodedata.html#module-unicodedata). Probablemente pueda ejecutar todos los puntos de código Unicode válidos y ejecutarlos a través de unicodedata.category(), y usar la salida para poblar el mapa ... – ThomasH

+0

Gracias por la sugerencia, puedo implementar eso algún día. El código anterior se creó primero para JavaScript (para el que había pocas alternativas sensatas en ese momento), y luego se transfirió a Python. Ejecuté algunas expresiones regulares en las especificaciones y terminé con un guión descartable, pero acepto que un procedimiento repetible hubiera sido mejor, así que puedo mantenerlo actualizado. – mgibsonbr

+0

He pirateado una función rápida que crea el mapa de forma dinámica (con solo listas de caracteres como valores): unicats (maxu): m = defaultdict (list) para i en rango (maxu): try : cat = unicodedata.category (unichr (i)) excepto: cat = Ninguno si el gato: m [cat] .Append (i) retorno m m = unicats (10FFFF) Tenga en cuenta que algunas categorías son realmente grandes (por ejemplo len (m ['Cn']) == 873882). – ThomasH