¿Cómo extraer números de una cadena en Python?

432

Extraería todos los números contenidos en una cadena. ¿Cuál es el más adecuado para el propósito, las expresiones regulares o el isdigit()método?

Ejemplo:

line = "hello 12 hi 89"

Resultado:

[12, 89]
pablouche
fuente

Respuestas:

485

Si solo desea extraer solo enteros positivos, intente lo siguiente:

>>> str = "h3110 23 cat 444.4 rabbit 11 2 dog"
>>> [int(s) for s in str.split() if s.isdigit()]
[23, 11, 2]

Yo diría que esto es mejor que el ejemplo de expresiones regulares por tres razones. Primero, no necesitas otro módulo; en segundo lugar, es más legible porque no necesita analizar el mini-lenguaje regex; y tercero, es más rápido (y, por lo tanto, probablemente más pitónico):

python -m timeit -s "str = 'h3110 23 cat 444.4 rabbit 11 2 dog' * 1000" "[s for s in str.split() if s.isdigit()]"
100 loops, best of 3: 2.84 msec per loop

python -m timeit -s "import re" "str = 'h3110 23 cat 444.4 rabbit 11 2 dog' * 1000" "re.findall('\\b\\d+\\b', str)"
100 loops, best of 3: 5.66 msec per loop

Esto no reconocerá flotantes, enteros negativos o enteros en formato hexadecimal. Si no puede aceptar estas limitaciones, la respuesta de Slim a continuación será suficiente.

fmark
fuente
55
esto fallará para casos como "h3110 23 gato 444.4 conejo 11-2 perro"
sharafjaffri
8
El caso normativo está utilizando re. Es una herramienta general y poderosa (para que aprendas algo muy útil). La velocidad es algo irrelevante en el análisis de registros (después de todo no es un solucionador numérico intensivo), el remódulo está en la biblioteca estándar de Python y no hace daño cargarlo.
Ioannis Filippidis
19
Tenía cadenas como mumblejumble45mumblejumbleen las que sabía que solo había un número. La solución es simple int(filter(str.isdigit, your_string)).
Jonas Lindeløv
1
Un comentario menor: usted define la variable strque luego anula el strobjeto y el método en Python base. Esa no es una buena práctica, ya que podría necesitarla más adelante en el script.
Jonas Lindeløv
11
int(filter(...))se elevará TypeError: int() argument must be a string...para Python 3.5, por lo que puede usar la versión actualizada: int(''.join(filter(str.isdigit, your_string)))para extraer todos los dígitos a un entero.
Mark Mishyn
449

Yo usaría una expresión regular:

>>> import re
>>> re.findall(r'\d+', 'hello 42 I\'m a 32 string 30')
['42', '32', '30']

Esto también coincidiría con 42 de bla42bla. Si solo desea números delimitados por límites de palabras (espacio, punto, coma), puede usar \ b:

>>> re.findall(r'\b\d+\b', 'he33llo 42 I\'m a 32 string 30')
['42', '32', '30']

Para terminar con una lista de números en lugar de una lista de cadenas:

>>> [int(s) for s in re.findall(r'\b\d+\b', 'he33llo 42 I\'m a 32 string 30')]
[42, 32, 30]
Vincent Savard
fuente
99
... y luego mapear intsobre él y listo. +1 especialmente para la última parte. Sin r'\b\d+\b' == '\\b\\d+\\b'embargo, sugeriría cadenas sin formato ( ).
55
Se podría poner en una lista con un generador, como:int_list = [int(s) for s in re.findall('\\d+', 'hello 12 hi 89')]
GreenMatt
77
@GreenMatt: eso es técnicamente una lista de comprensión (no un generador), pero estaría de acuerdo en que las comprensiones / generadores son más Pythonic que map.
Seth Johnson el
1
@Seth Johnson: ¡Uy! Tienes razón, escribí mal en lo que aparentemente era un estado mental empañado. :-( ¡Gracias por la corrección!
GreenMatt
2
Aunque tengo un problema. ¿Qué pasa si quiero extraer números flotantes también como 1.45 en "hola1.45 hola". Me dará 1 y 45 como dos números diferentes
ab123
89

Esto es más que un poco tarde, pero también puede extender la expresión regex para tener en cuenta la notación científica.

import re

# Format is [(<string>, <expected output>), ...]
ss = [("apple-12.34 ba33na fanc-14.23e-2yapple+45e5+67.56E+3",
       ['-12.34', '33', '-14.23e-2', '+45e5', '+67.56E+3']),
      ('hello X42 I\'m a Y-32.35 string Z30',
       ['42', '-32.35', '30']),
      ('he33llo 42 I\'m a 32 string -30', 
       ['33', '42', '32', '-30']),
      ('h3110 23 cat 444.4 rabbit 11 2 dog', 
       ['3110', '23', '444.4', '11', '2']),
      ('hello 12 hi 89', 
       ['12', '89']),
      ('4', 
       ['4']),
      ('I like 74,600 commas not,500', 
       ['74,600', '500']),
      ('I like bad math 1+2=.001', 
       ['1', '+2', '.001'])]

for s, r in ss:
    rr = re.findall("[-+]?[.]?[\d]+(?:,\d\d\d)*[\.]?\d*(?:[eE][-+]?\d+)?", s)
    if rr == r:
        print('GOOD')
    else:
        print('WRONG', rr, 'should be', r)

Da todo bien!

Además, puede ver la expresión regular incorporada de AWS Glue

aidan.plenert.macdonald
fuente
1
Como esta es la única respuesta que le gusta a alguien, he aquí cómo hacerlo con la notación científica "[- +]? \ D + [\.]? \ D * [Ee]? \ D *". O alguna variación. ¡Que te diviertas!
aidan.plenert.macdonald
Encuentra que hay un problema con el caso más simple, por ejemplo, s = "4"no devuelve coincidencias. ¿Se puede editar para que también se encargue de esto?
batFINGER
1
agradable pero no maneja comas (por ejemplo
74,600
Un grupo más detallado es [+-]?\d*[\.]?\d*(?:(?:[eE])[+-]?\d+)?Este grupo da algunos falsos positivos ( +es decir , a veces se captura solo), pero puede manejar más formas, como .001, además, no combina números automáticamente (como en s=2+1)
DavisDude
24
Ah sí, lo obvio [-+]?[.]?[\d]+(?:,\d\d\d)*[\.]?\d*(?:[eE][-+]?\d+)?, tan tonto de mi parte ... ¿cómo podría no pensar en eso?
Przemek D
70

Supongo que quieres flotadores no solo enteros, así que haría algo como esto:

l = []
for t in s.split():
    try:
        l.append(float(t))
    except ValueError:
        pass

Tenga en cuenta que algunas de las otras soluciones publicadas aquí no funcionan con números negativos:

>>> re.findall(r'\b\d+\b', 'he33llo 42 I\'m a 32 string -30')
['42', '32', '30']

>>> '-3'.isdigit()
False
jmnas
fuente
Esto encuentra flotantes y enteros positivos y negativos. Para enteros positivos y negativos, cambie floata int.
Hugo
3
Para números negativos:re.findall("[-\d]+", "1 -2")
ytpillai
¿Hay alguna diferencia si escribimos en continuelugar de passen el bucle?
D. Jones
Esto atrapa más que solo números enteros positivos, pero al usar split () se perderán los números que tienen símbolos de moneda que preceden al primer dígito sin espacio, lo cual es común en los documentos financieros
Marc Maxmeister
No funciona para carrozas que no tienen espacio con otros caracteres, por ejemplo: '4.5 k cosas' funcionará, '4.5k cosas' no.
Jay D.
64

Si sabe que solo habrá un número en la cadena, es decir, 'hola 12 hola', puede intentar filtrar.

Por ejemplo:

In [1]: int(''.join(filter(str.isdigit, '200 grams')))
Out[1]: 200
In [2]: int(''.join(filter(str.isdigit, 'Counters: 55')))
Out[2]: 55
In [3]: int(''.join(filter(str.isdigit, 'more than 23 times')))
Out[3]: 23

Pero ten cuidado! :

In [4]: int(''.join(filter(str.isdigit, '200 grams 5')))
Out[4]: 2005
dfostic
fuente
12
En Python 3.6.3 Me TypeError: int() argument must be a string, a bytes-like object or a number, not 'filter'- fijándolo mediante el usoint("".join(filter(str.isdigit, '200 grams')))
Kent Munthe Caspersen
16
# extract numbers from garbage string:
s = '12//n,_@#$%3.14kjlw0xdadfackvj1.6e-19&*ghn334'
newstr = ''.join((ch if ch in '0123456789.-e' else ' ') for ch in s)
listOfNumbers = [float(i) for i in newstr.split()]
print(listOfNumbers)
[12.0, 3.14, 0.0, 1.6e-19, 334.0]
AndreiS
fuente
3
Bienvenido a SO y gracias por publicar una respuesta. Siempre es una buena práctica agregar algunos comentarios adicionales a su respuesta y por qué resuelve el problema, en lugar de simplemente publicar un fragmento de código.
sebs
no funcionó en mi caso. no muy diferente de la respuesta anterior
oldboy
ValueError: no se pudo convertir la cadena en flotante: 'e' y en algunos casos no funciona :(
Vilq
15

Estaba buscando una solución para eliminar las máscaras de cadenas, específicamente de los números de teléfono brasileños, esta publicación no respondió pero me inspiró. Esta es mi solución:

>>> phone_number = '+55(11)8715-9877'
>>> ''.join([n for n in phone_number if n.isdigit()])
'551187159877'
Sidon
fuente
12

Usar Regex a continuación es la forma

lines = "hello 12 hi 89"
import re
output = []
#repl_str = re.compile('\d+.?\d*')
repl_str = re.compile('^\d+$')
#t = r'\d+.?\d*'
line = lines.split()
for word in line:
        match = re.search(repl_str, word)
        if match:
            output.append(float(match.group()))
print (output)

con findall re.findall(r'\d+', "hello 12 hi 89")

['12', '89']

re.findall(r'\b\d+\b', "hello 12 hi 89 33F AC 777")

 ['12', '89', '777']
sim
fuente
Al menos debería compilar la expresión regular si no la está utilizandofindall()
information_interchange
2
repl_str = re.compile('\d+.?\d*') debería ser: repl_str = re.compile('\d+\.?\d*') Para un ejemplo reproducible usando python3.7 re.search(re.compile(r'\d+.?\d*'), "42G").group() '42G' re.search(re.compile(r'\d+\.?\d*'), "42G").group() '42'
Alexis Lucattini
8
line2 = "hello 12 hi 89"
temp1 = re.findall(r'\d+', line2) # through regular expression
res2 = list(map(int, temp1))
print(res2)

Hola ,

Puede buscar todos los enteros en la cadena a través del dígito utilizando la expresión Findall.

En el segundo paso, cree una lista res2 y agregue los dígitos encontrados en la cadena a esta lista

espero que esto ayude

Saludos, Diwakar Sharma

Diwakar SHARMA
fuente
La respuesta proporcionada se marcó para su revisión como Publicación de baja calidad. Aquí hay algunas pautas para ¿Cómo escribo una buena respuesta? . Esta respuesta proporcionada puede ser correcta, pero podría beneficiarse de una explicación. Las respuestas de solo código no se consideran respuestas "buenas". De la revisión .
Trenton McKinney
solución simple y funcional, apreciada
moyo
7

Esta respuesta también contiene el caso cuando el número es flotante en la cadena

def get_first_nbr_from_str(input_str):
    '''
    :param input_str: strings that contains digit and words
    :return: the number extracted from the input_str
    demo:
    'ab324.23.123xyz': 324.23
    '.5abc44': 0.5
    '''
    if not input_str and not isinstance(input_str, str):
        return 0
    out_number = ''
    for ele in input_str:
        if (ele == '.' and '.' not in out_number) or ele.isdigit():
            out_number += ele
        elif out_number:
            break
    return float(out_number)
Menglong Li
fuente
5

Me sorprende ver que nadie ha mencionado aún el uso de itertools.groupby una alternativa para lograr esto.

Puede usar itertools.groupby()junto con str.isdigit()para extraer números de la cadena como:

from itertools import groupby
my_str = "hello 12 hi 89"

l = [int(''.join(i)) for is_digit, i in groupby(my_str, str.isdigit) if is_digit]

El valor retenido lserá:

[12, 89]

PD: Esto es solo con fines ilustrativos para mostrar que, como alternativa, también podríamos usar groupbypara lograr esto. Pero esta no es una solución recomendada. Si desea lograr esto, debe usar la respuesta aceptada de fmark basada en la comprensión de la lista con un str.isdigitfiltro.

Moinuddin Quadri
fuente
4

Solo estoy agregando esta respuesta porque nadie agregó una usando el manejo de excepciones y porque esto también funciona para flotantes

a = []
line = "abcd 1234 efgh 56.78 ij"
for word in line.split():
    try:
        a.append(float(word))
    except ValueError:
        pass
print(a)

Salida:

[1234.0, 56.78]
Raghav
fuente
4

Para atrapar diferentes patrones, es útil consultar con diferentes patrones.

Configure todos los patrones que capturan diferentes patrones de números de interés:

(encuentra comas) 12,300 o 12,300.00

'[\ d] + [., \ d] +'

(encuentra flotantes) 0.123 o .123

'[\ d] * [.] [\ d] +'

(encuentra números enteros) 123

'[\ d] +'

Combine con tubería (|) en un patrón con múltiples o condicionales .

(Nota: Ponga patrones complejos primero; de lo contrario, los patrones simples devolverán fragmentos de la captura compleja en lugar de que la captura compleja devuelva la captura completa).

p = '[\d]+[.,\d]+|[\d]*[.][\d]+|[\d]+'

A continuación, confirmaremos que hay un patrón presente re.search()y luego devolveremos una lista iterable de capturas. Finalmente, imprimiremos cada captura utilizando la notación de paréntesis para subseleccionar el valor de retorno del objeto coincidente del objeto coincidente.

s = 'he33llo 42 I\'m a 32 string 30 444.4 12,001'

if re.search(p, s) is not None:
    for catch in re.finditer(p, s):
        print(catch[0]) # catch is a match object

Devoluciones:

33
42
32
30
444.4
12,001
James Andrew Bush
fuente
2

Como ninguno de estos se refería a números financieros del mundo real en documentos de Excel y Word que necesitaba encontrar, aquí está mi variación. Maneja ints, flotantes, números negativos, números de moneda (porque no responde en división), y tiene la opción de soltar la parte decimal y solo devolver ints, o devolver todo.

También maneja el sistema de números de Indian Laks donde las comas aparecen de manera irregular, no cada 3 números separados.

No maneja la notación científica o los números negativos entre paréntesis en los presupuestos, parecerán positivos.

Tampoco extrae fechas. Hay mejores formas de encontrar fechas en cadenas.

import re
def find_numbers(string, ints=True):            
    numexp = re.compile(r'[-]?\d[\d,]*[\.]?[\d{2}]*') #optional - in front
    numbers = numexp.findall(string)    
    numbers = [x.replace(',','') for x in numbers]
    if ints is True:
        return [int(x.replace(',','').split('.')[0]) for x in numbers]            
    else:
        return numbers
Marc Maxmeister
fuente
1

@jmnas, me gustó tu respuesta, pero no encontró flotadores. Estoy trabajando en un script para analizar el código que va a un molino CNC y necesitaba encontrar las dimensiones X e Y que pueden ser enteros o flotantes, por lo que adapté su código a lo siguiente. Esto encuentra int, float con valores positivos y negativos. Todavía no encuentra valores con formato hexadecimal, pero podría agregar "x" y "A" a través de "F" a la num_chartupla y creo que analizaría cosas como '0x23AC'.

s = 'hello X42 I\'m a Y-32.35 string Z30'
xy = ("X", "Y")
num_char = (".", "+", "-")

l = []

tokens = s.split()
for token in tokens:

    if token.startswith(xy):
        num = ""
        for char in token:
            # print(char)
            if char.isdigit() or (char in num_char):
                num = num + char

        try:
            l.append(float(num))
        except ValueError:
            pass

print(l)
ZacSketches
fuente
0

La mejor opción que encontré está debajo. Extraerá un número y puede eliminar cualquier tipo de carbón.

def extract_nbr(input_str):
    if input_str is None or input_str == '':
        return 0

    out_number = ''
    for ele in input_str:
        if ele.isdigit():
            out_number += ele
    return float(out_number)    
Ajay Kumar
fuente
0

Para los números de teléfono, simplemente puede excluir todos los caracteres que no sean dígitos con \ D en regex:

import re

phone_number = '(619) 459-3635'
phone_number = re.sub(r"\D", "", phone_number)
print(phone_number)
Antonin GAVREL
fuente