Cómo dividir reglas largas de expresiones regulares en varias líneas en Python

¿Es esto realmente factible? Tengo algunas reglas de patrones de expresiones regulares muy largas que son difíciles de entender porque no encajan en la pantalla a la vez. Ejemplo:

test = re.compile('(?P.+):\d+:\s+warning:\s+Member\s+(?P.+)\s+\((?P%s)\) of (class|group|namespace)\s+(?P.+)\s+is not documented' % (self.__MEMBER_TYPES), re.IGNORECASE) 

Backslash o comillas triples no funcionarán.

EDITAR. Terminé usando el modo VERBOSE. Así es como se ve el patrón de expresión regular ahora:

 test = re.compile(''' (?P # Capture a group called full_path .+ # It consists of one more characters of any type ) # Group ends : # A literal colon \d+ # One or more numbers (line number) : # A literal colon \s+warning:\s+parameters\sof\smember\s+ # An almost static string (?P # Capture a group called member_name [ # ^: # Match anything but a colon (so finding a colon ends group) ]+ # Match one or more characters ) # Group ends ( # Start an unnamed group :: # Two literal colons (?P # Start another group called function_name \w+ # It consists on one or more alphanumeric characters ) # End group )* # This group is entirely optional and does not apply to C \s+are\snot\s\(all\)\sdocumented''', # And line ends with an almost static string re.IGNORECASE|re.VERBOSE) # Let's not worry about case, because it seems to differ between Doxygen versions 

Puede dividir su patrón de expresiones regulares citando cada segmento. No se necesitan barras invertidas.

 test = re.compile(('(?P.+):\d+:\s+warning:\s+Member' '\s+(?P.+)\s+\((?P%s)\) ' 'of (class|group|namespace)\s+(?P.+)' '\s+is not documented') % (self.__MEMBER_TYPES), re.IGNORECASE) 

También puede usar el indicador de cadena sin formato 'r' y deberá colocarlo antes de cada segmento.

Ver los documentos .

De http://docs.python.org/reference/lexical_analysis.html#string-literal-concatenation :

Se permiten múltiples literales de cadena adyacentes (delimitados por espacios en blanco), posiblemente utilizando diferentes convenciones de comillas, y su significado es el mismo que su concatenación. Por lo tanto, “hola” ‘mundo’ es equivalente a “helloworld”. Esta función se puede usar para reducir el número de barras invertidas necesarias, para dividir cadenas largas de manera conveniente en líneas largas, o incluso para agregar comentarios a partes de cadenas, por ejemplo:

 re.compile("[A-Za-z_]" # letter or underscore "[A-Za-z0-9_]*" # letter, digit or underscore ) 

Tenga en cuenta que esta característica se define en el nivel sintáctico, pero se implementa en tiempo de comstackción. El operador ‘+’ debe utilizarse para concatenar expresiones de cadena en tiempo de ejecución. También tenga en cuenta que la concatenación literal puede usar diferentes estilos de comillas para cada componente (incluso mezclando cadenas en bruto y cadenas con comillas triples).

Personalmente, no uso re.VERBOSE porque no me gusta escapar de los espacios en blanco y no quiero poner ‘\ s’ en lugar de espacios en blanco cuando ‘\ s’ no es necesario.
Cuanto más precisos sean los símbolos en un patrón de expresión regular en relación con las secuencias de caracteres que deben capturarse, más rápido actúa el objeto de expresión regular. Casi nunca uso ‘\ s’

.

Para evitar re.VERBOSE , puede hacer lo que ya se ha dicho:

 test = re.compile( '(?P.+)' ':\d+:\s+warning:\s+Member\s+' # comment '(?P.+)' '\s+\(' '(?P%s)' # comment '\) of ' '(class|group|namespace)' # ^^^^^^ underlining something to point out '\s+' '(?P.+)' # vvv overlining something important too '\s+is not documented'\ % (self.__MEMBER_TYPES), re.IGNORECASE) 

Empujar las cuerdas hacia la izquierda le da mucho espacio para escribir comentarios.

.

Pero esta manera no es tan buena cuando el patrón es muy largo porque no es posible escribir

 test = re.compile( '(?P.+)' ':\d+:\s+warning:\s+Member\s+' # comment '(?P.+)' '\s+\(' '(?P%s)' % (self.__MEMBER_TYPES) # !!!!!! INCORRECT SYNTAX !!!!!!! '\) of ' '(class|group|namespace)' # ^^^^^^ underlining something to point out '\s+' '(?P.+)' # vvv overlining something important too '\s+is not documented', re.IGNORECASE) 

entonces, en caso de que el patrón sea muy largo, el número de líneas entre
la parte % (self.__MEMBER_TYPES) al final
y la cadena '(?P%s)' a la que se aplica
Puede ser grande y perdemos la facilidad de leer el patrón.

.

Por eso me gusta usar una tupla para escribir un patrón muy largo:

 pat = ''.join(( '(?P.+)', # you can put a comment here, you see: a very very very long comment ':\d+:\s+warning:\s+Member\s+', '(?P.+)', '\s+\(', '(?P%s)' % (self.__MEMBER_TYPES), # comment here '\) of ', # comment here '(class|group|namespace)', # ^^^^^^ underlining something to point out '\s+', '(?P.+)', # vvv overlining something important too '\s+is not documented')) 

.

Esta manera permite definir el patrón como una función:

 def pat(x): return ''.join((\ '(?P.+)', # you can put a comment here, you see: a very very very long comment ':\d+:\s+warning:\s+Member\s+', '(?P.+)', '\s+\(', '(?P%s)' % x , # comment here '\) of ', # comment here '(class|group|namespace)', # ^^^^^^ underlining something to point out '\s+', '(?P.+)', # vvv overlining something important too '\s+is not documented')) test = re.compile(pat(self.__MEMBER_TYPES), re.IGNORECASE) 

Sólo para completar, la respuesta que falta aquí es usar el re.X o re.VERBOSE , que el OP finalmente señaló. Además de guardar citas, este método también es portátil en otras implementaciones de expresiones regulares como Perl.

Desde https://docs.python.org/2/library/re.html#re.X :

 re.X re.VERBOSE 

Este indicador le permite escribir expresiones regulares que se ven mejor y son más legibles al permitirle separar visualmente las secciones lógicas del patrón y agregar comentarios. El espacio en blanco dentro del patrón se ignora, excepto cuando se encuentra en una clase de caracteres o cuando está precedido por una barra invertida sin escapar. Cuando una línea contiene un # que no está en una clase de caracteres y no está precedida por una barra invertida que no se ha escapado, se ignoran todos los caracteres desde el extremo izquierdo como el # hasta el final de la línea.

Esto significa que los dos siguientes objetos de expresiones regulares que coinciden con un número decimal son funcionalmente iguales:

 a = re.compile(r"""\d + # the integral part \. # the decimal point \d * # some fractional digits""", re.X) 

 b = re.compile(r"\d+\.\d*") 

El comstackdor de Python concatenará automáticamente literales de cadena adyacentes. Por lo tanto, una manera de hacer esto es dividir su expresión regular en varias cadenas, una en cada línea, y dejar que el comstackdor de Python las combine. No importa el espacio en blanco que tenga entre las cadenas, por lo que puede tener saltos de línea e incluso espacios iniciales para alinear los fragmentos de manera significativa.

Use la concatenación de cadenas como en la respuesta de naeg o use re.VERBOSE / re.X, pero tenga cuidado de que esta opción ignorará los espacios en blanco y los comentarios. Tiene algunos espacios en su expresión regular, por lo que se ignorarán y tendrá que escapar de ellos o usar \s

Así por ejemplo

 test = re.compile("""(?P.+):\d+: # some comment \s+warning:\s+Member\s+(?P.+) #another comment \s+\((?P%s)\)\ of\ (class|group|namespace)\s+ (?P.+)\s+is\ not\ documented""" % (self.__MEMBER_TYPES), re.IGNORECASE | re.X)