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

539

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

Ejemplo:

line = "hello 12 hi 89"

Resultado:

[12, 89]
1
  • 3
    Desafortunadamente, los datos de entrada de muestra eran tan simplistas, ya que invitaban a soluciones ingenuas. Los casos comunes deben manejar cadenas de entrada con caracteres más interesantes adyacentes a los dígitos. Una entrada un poco más desafiante:'''gimme digits from "12", 34, '56', -789.'''
    MarkHu
    20/08/20 a las 19:27
605

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

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

Yo diría que esto es mejor que el ejemplo de expresiones regulares porque no necesita otro módulo y es más legible porque no necesita analizar (y aprender) el mini lenguaje de expresiones regulares .

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

17
  • 5
    esto fallará para casos como "h3110 23 gato 444.4 conejo 11-2 perro" 4 de diciembre de 2013 a las 8:15
  • 9
    El caso normativo está usando 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 está de más cargarlo. 22/04/2014 a las 7:27
  • 39
    Tenía cadenas como mumblejumble45mumblejumbleen las que sabía que solo había un número. La solución es sencilla int(filter(str.isdigit, your_string)). 20/08/15 a las 9:57
  • 2
    Un comentario menor: define la variable strque luego anula el strobjeto y el método en Python base. Esa no es una buena práctica, ya que es posible que la necesite más adelante en el guión. 20/08/15 a las 9:58
  • 39
    int(filter(...))aumentará 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. 21 de marzo de 2017 a las 7:51
566

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]
7
  • 9
    ... y luego mapearlo inty listo. +1 especialmente para la última parte. Sin r'\b\d+\b' == '\\b\\d+\\b'embargo, sugeriría cadenas sin procesar ( ).
    user395760
    27/11/10 a las 0:06
  • 6
    Podría incluirse en una lista con un generador, como por ejemplo: int_list = [int(s) for s in re.findall('\\d+', 'hello 12 hi 89')] 27/11/10 a las 0:19
  • 7
    @GreenMatt: técnicamente es una lista de comprensión (no un generador), pero estaría de acuerdo en que las comprensiones / generadores son más Pythonic que map. 27/11/10 a las 1:23
  • 1
    @Seth Johnson: ¡Ups! Tienes razón, escribí mal en lo que aparentemente era un estado mental confuso. :-( ¡Gracias por la corrección! 28/11/10 a las 14:57
  • 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
    24 de mayo de 2018 a las 5:17
98

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

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 consultar la expresión regular incorporada de AWS Glue

7
  • 1
    Como esta es la única respuesta que le gusta a alguien, aquí se explica cómo hacerlo con la notación científica "[- +]? \ D + [\.]? \ D * [Ee]? \ D *". O alguna variación. ¡Divertirse! 6/11/15 a las 15:12
  • Encuentra que hay un problema con el caso más simple, por ejemplo, s = "4"no devuelve coincidencias. ¿Se puede volver a editar para que también se encargue de esto? 10/10/2016 a las 13:03
  • 1
    agradable pero no admite comas (por ejemplo, 74,600)
    yekta
    11/10/2016 a las 14:54
  • Un grupo más detallado es [+-]?\d*[\.]?\d*(?:(?:[eE])[+-]?\d+)?Este grupo da algunos falsos positivos ( +es decir , a veces se captura solo), pero es capaz de manejar más formas, como .001, además de que no combina números automáticamente (como en s=2+1) 16 de marzo de 2017 a las 16:34
  • 37
    Ah, sí, lo obvio [-+]?[.]?[\d]+(?:,\d\d\d)*[\.]?\d*(?:[eE][-+]?\d+)?, tan tonto de mi parte ... ¿cómo no iba a pensar en eso? 4 oct 2017 a las 11:52
82

Supongo que quieres flotantes no solo números 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
6
  • Esto encuentra números enteros y flotantes positivos y negativos. Para números enteros positivos y negativos, cambie floata int.
    Hugo
    2/06/2015 a las 12:34
  • 5
    Para números negativos: re.findall("[-\d]+", "1 -2") 15 de septiembre de 2015 a las 19:03
  • ¿Hay alguna diferencia si escribimos en continuelugar de passen el bucle? 15 de agosto de 2016 a las 10:48
  • Esto captura más que números enteros positivos, pero al usar split () se perderán los números que tienen símbolos de moneda antes del primer dígito sin espacio, lo cual es común en los documentos financieros. 2 de junio de 2017 a las 13:12
  • 1
    No funciona para flotantes que no tienen espacio con otros caracteres, por ejemplo: '4.5 k cosas' funcionarán, '4.5k cosas' no.
    Jay D.
    21/06/18 a las 18:01
75

Si sabe que solo habrá un número en la cadena, es decir 'hello 12 hi', puede intentarlo filter.

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 cuidado !!! :

In [4]: int(''.join(filter(str.isdigit, '200 grams 5')))
Out[4]: 2005
1
  • 13
    En Python 3.6.3 obtuve TypeError: int() argument must be a string, a bytes-like object or a number, not 'filter'- arreglándolo usandoint("".join(filter(str.isdigit, '200 grams'))) 9 de abril de 2018 a las 8:56
23

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

>>> phone_number = '+55(11)8715-9877'
>>> ''.join([n for n in phone_number if n.isdigit()])
'551187159877'
2
  • Agradable y simple, y posiblemente más legible que la filter()técnica de función también correcta pero menos conocida :''.join(filter(str.isdigit, phone_number))
    MarkHu
    20/08/20 a las 19:43
  • 1
    Agradable, pero la conversión a lista no es necesaria. Se puede mejorar ligeramente como ''.join(n for n in phone_number if n.isdigit()).
    AnT
    21 de julio a las 3:46
21

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

Configure todos los patrones que capten diferentes patrones numéricos 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 pipe (|) en un patrón con múltiples o condicionales .

(Nota: coloque los 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 la presencia de un patrón re.search()y luego devolveremos una lista iterable de capturas. Finalmente, imprimiremos cada captura usando la notación entre corchetes para subseleccionar el valor de retorno del objeto de coincidencia del objeto de coincidencia.

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
1
  • Esto también aceptará un número que termine con un punto, como "30". Necesitas algo así: "[\ d] + [\, \ d] * [\.] {0,1} [\ d] +" 27 de enero a las 12:16
20
# extract numbers from garbage string:
s = '12//n,[email protected]#$%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]
3
  • 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
    29/03/18 a las 13:48
  • no funcionó en mi caso. no muy diferente de la respuesta anterior
    oldboy
    6 de julio de 2018 a las 3:43
  • ValueError: no se pudo convertir la cadena en flotante: 'e' y no funciona en algunos casos :(
    Vilq
    6/09/19 a las 11:27
19

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']
2
  • Debería al menos compilar la expresión regular si no está usando findall() 18/10/19 a las 3:21
  • 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' 10/11/19 a las 5:47
16

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

import re

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

La ren r"\D"significa cuerda cruda . Es necesario. Sin él, Python lo considerará \Dcomo un carácter de escape.

9
line2 = "hello 12 hi 89"  # this is the given string 
temp1 = re.findall(r'\d+', line2) # find number of digits through regular expression
res2 = list(map(int, temp1))
print(res2)

Hola ,

puede buscar todos los enteros en la cadena a través de dígitos utilizando la expresión findall.

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

espero que esto ayude

Saludos, Diwakar Sharma

1
  • 2
    La respuesta proporcionada se marcó para su revisión como una 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 revisión . 6/10/19 a las 0:36
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)
6

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

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

Producción :

[1234.0, 56.78]
5

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

Puede usar itertools.groupby()junto con str.isdigit()para extraer números de una 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 por lserá:

[12, 89]

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

2

@jmnas, me gustó tu respuesta, pero no encontró flotadores. Estoy trabajando en un script para analizar el código que va a una fresadora CNC y necesitaba encontrar las dimensiones X e Y que pueden ser enteros o flotantes, así 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 "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)
2

Dado que ninguno de estos trató con números financieros del mundo real en Excel y documentos de Word que necesitaba encontrar, aquí está mi variación. Maneja ints, flotadores, números negativos, números de moneda (porque no responde al dividir) y tiene la opción de eliminar la parte decimal y devolver ints o devolver todo.

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

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

Tampoco extrae dátiles. Hay mejores formas de buscar 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
0

La mejor opción que encontré está a continuación. Extraerá un número y podrá eliminar cualquier tipo de carbonilla.

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)