Python: Análisis de valores numéricos a partir de cadenas usando expresiones regulares

Estoy escribiendo código de Python para analizar diferentes tipos de números de una cadena usando expresiones regulares y me he encontrado con un problema molesto que no entiendo.

Mi código es el siguiente:

import re test_string = "Distributions $54.00 bob $26 and 0.30 5% ($0.23) 2,333,450" num_values = re.findall(r"\(?\$?[0-9]+.?[0-9]*%?\)?|[0-9]+(?:,[0-9]+)*", test_string) 

La salida es:

 ['$54.00', '0.30', '5%', '($0.23)', '2,333', '450'] 

Así que el código está funcionando bien para todo, pero la cadena ‘2,333,450’ que por alguna razón se corta en dos elementos.

Lo molesto es que cuando invierto el orden de la expresión regular:

 num_values = re.findall(r"[0-9]+(?:,[0-9]+)*|\(?\$?[0-9]+.?[0-9]*%?\)?", test_string) 

Obtengo esta salida:

 ['$54.00', '$26 ', '0', '30', '5', '($0.23)', '2,333,450'] 

Así que mi cadena ‘2,333,450’ sale bien pero luego no puedo analizar correctamente los números con% de símbolos o puntos decimales (a menos que estén entre paréntesis).

Cualquier idea sería apreciada.

En primer lugar, sospecho que el período en la primera parte de la expresión regular debe escaparse con una barra invertida inicial (si se pretende que coincida con un punto decimal), actualmente coincide con cualquier carácter, por lo que tiene una coincidencia que contiene un espacio '$26 ' .

Por lo tanto, el 2,333 se corresponde con la primera parte de su expresión regular (la , coincide con la que no se escapó), razón por la cual no coincidió con la parte de ,450 de ese número.

Si bien su expresión regular (corregida) trabaja con sus datos de muestra limitados que pueden ser lo suficientemente buenos, puede ser demasiado amplia para uso general, por ejemplo, coincide ($1267.3% . Puede acumular una expresión regular más grande con partes más pequeñas, sin embargo, esto puede ponerse feo rápido

 import re test_string = "Distributions $54.00 bob $26 and 0.30 5% ($0.23) 2,333,450" test_string += " $12,354.00 43 43.12 1234,12 ($123,456.78" COMMA_SEP_NUMBER = r'\d{1,3}(?:,\d{3})*' # require groups of 3 DECIMAL_NUMBER = r'\d+(?:\.\d*)?' COMMA_SEP_DECIMAL = COMMA_SEP_NUMBER + r'(?:\.(?:\d{3},)*\d{0,3})?' # are commas used after the decimal point? regex_items = [] regex_items.append('\$' + COMMA_SEP_DECIMAL) regex_items.append('\$' + DECIMAL_NUMBER) regex_items.append(COMMA_SEP_DECIMAL + '\%') regex_items.append(DECIMAL_NUMBER + '\%') regex_items.append(COMMA_SEP_DECIMAL) regex_items.append(DECIMAL_NUMBER) r = re.compile('|'.join(regex_items)) print r.findall(test_string) 

Tenga en cuenta que esto no tiene en cuenta el paréntesis alrededor de los números, y falla en 1234,12 (que probablemente debería interpretarse como dos números 1234 y 12 ) debido a la comparación de 123 con el patrón COMMA_SEP_NUMBER.

Este es un problema con esta técnica porque si el patrón DECIMAL_NUMBER aparece primero, COMMA_SEP_NUMBER nunca coincidirá.

Finalmente, aquí hay una buena herramienta para visualizar expresiones regulares

 \d{1,3}(?:,\d{3})*(?:\.(?:\d{3},)*\d{0,3})? 

Visualización de expresiones regulares

Demo Debuggex

¿Qué tal fusionar dos partes en una?

 >>> test_string = "Distributions $54.00 bob $26 and 0.30 5% ($0.23) 2,333,450" >>> re.findall(r'\(?\$?\d+(?:,\d+)*\.?\d*%?\)?', test_string) ['$54.00', '$26', '0.30', '5%', '($0.23)', '2,333,450'] 
  • Sustituido . con \. para hacer coincidir el punto literalmente en lugar de coincidir con cualquier personaje.
  • Sustituido [0-9] con \d . ( \d coincide con el dígito)