Como extrair números de uma string em Python?


432

Eu extrairia todos os números contidos em uma string. Qual é o mais adequado para a finalidade, expressões regulares ou o isdigit()método?

Exemplo:

line = "hello 12 hi 89"

Resultado:

[12, 89]

Respostas:


485

Se você deseja extrair apenas números inteiros positivos, tente o seguinte:

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

Eu diria que isso é melhor do que o exemplo regex por três razões. Primeiro, você não precisa de outro módulo; segundo, é mais legível porque você não precisa analisar a minilíngua do regex; e terceiro, é mais rápido (e, portanto, provavelmente mais 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

Isso não reconhecerá flutuadores, números inteiros negativos ou números inteiros no formato hexadecimal. Se você não puder aceitar essas limitações, a resposta de Slim abaixo fará o truque.


5
isto irá falhar por caso como "h3110 23 gato 444,4 coelho 11-2 cão"
— sharafjaffri

8
O caso normativo está usando re. É uma ferramenta geral e poderosa (para você aprender algo muito útil). A velocidade é um tanto irrelevante na análise de logs (afinal, não é um solucionador numérico intensivo), o remódulo está na biblioteca padrão do Python e não custa carregá-lo.
— Ioannis Filippidis

19
Eu tinha cordas como as mumblejumble45mumblejumbleque eu sabia que havia apenas um número. A solução é simples int(filter(str.isdigit, your_string)).
— Jonas Lindeløv 20/08/2015

1
Um comentário secundário: você define a variável strque substitui o strobjeto e o método no python base. Isso não é uma boa prática, pois você pode precisar mais tarde no script.
— Jonas Lindeløv 20/08/2015

11
int(filter(...))aumentará TypeError: int() argument must be a string...para o Python 3.5, para que você possa usar a versão atualizada: int(''.join(filter(str.isdigit, your_string)))para extrair todos os dígitos para um número inteiro.
— Mark Mishyn

449

Eu usaria um regexp:

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

Isso também corresponderia a 42 de bla42bla. Se você deseja apenas números delimitados por limites de palavras (espaço, ponto, vírgula), use \ b:

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

Para terminar com uma lista de números em vez de uma lista de cadeias de caracteres:

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

9
... e depois mapeie inte pronto. +1 especialmente para a última parte. Eu sugeriria strings cruas ( r'\b\d+\b' == '\\b\\d+\\b').

5
Pode ser colocado em uma lista com um gerador, como:int_list = [int(s) for s in re.findall('\\d+', 'hello 12 hi 89')]
— GreenMatt 27/11

7
@ GreenMatt: isso é tecnicamente uma compreensão de lista (não um gerador), mas eu concordo que as compreensões / geradores são mais pitonicos do que map.
— Seth Johnson

1
@ Johnson Johnson: Opa! Você está certo, eu digitei errado no que aparentemente era um estado mental embaçado. :-( Obrigado pela correção!
— GreenMatt 28/11

2
Eu tenho um problema. E se eu quiser extrair números flutuantes também como 1,45 em "hello1.45 oi". Ele vai me dar 1 e 45 como dois números diferentes
— AB123

89

Isso é mais que um pouco tarde, mas você pode estender a expressão regex para levar em conta também a notação 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)

Dá tudo de bom!

Além disso, você pode consultar o regex interno do AWS Glue


1
Como esta é a única resposta que alguém gosta, eis como fazê-lo com a notação científica "[- +]? \ D + [\.]? \ D * [Ee]? \ D *". Ou alguma variação. Diverta-se!
— Aidan.plenert.macdonald

Encontre que há um problema com o caso mais simples, por exemplo, s = "4"não retorna correspondências. Pode ser editado para também cuidar disso?
— batFINGER

1
bom, mas não lidam com vírgulas (por exemplo, 74.600)
— Yekta

Um grupo mais detalhado é [+-]?\d*[\.]?\d*(?:(?:[eE])[+-]?\d+)?Este grupo dá alguns falsos positivos (ou seja, +é capturado por si só às vezes), mas é capaz de lidar com mais formas, como .001, além de que não combinam números automaticamente (como em s=2+1)
— DavisDude

24
Ah, sim, o óbvio [-+]?[.]?[\d]+(?:,\d\d\d)*[\.]?\d*(?:[eE][-+]?\d+)?- tão bobo da minha parte ... como eu não conseguia pensar nisso?
— Przemek D

70

Suponho que você queira carros alegóricos, não apenas números inteiros, então eu faria algo assim:

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

Observe que algumas das outras soluções postadas aqui não funcionam com números negativos:

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

>>> '-3'.isdigit()
False

Este encontra flutuadores e números inteiros positivos e negativos. Para números inteiros positivos e negativos, altere floatpara int.
— Hugo

3
Para números negativos:re.findall("[-\d]+", "1 -2")
— ytpillai 15/09/2015

Será que faz alguma diferença se escrevermos em continuevez de passno loop?
— D. Jones

Mais do que apenas números inteiros positivos, mas usando split () vai perder números que têm símbolos de moeda que precedem o primeiro dígito, sem espaço, o que é comum em documentos financeiros Isso chama
— Marc Maxmeister

Não funciona para carros alegóricos que não têm espaço com outros caracteres, por exemplo: '4.5k coisas' funcionarão, '4.5k coisas' não.
— Jay D.

64

Se você sabe que haverá apenas um número na string, ou seja, 'olá 12 oi', você pode tentar filtrar.

Por exemplo:

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

Mas tenha cuidado !!! :

In [4]: int(''.join(filter(str.isdigit, '200 grams 5')))
Out[4]: 2005

12
Em Python 3.6.3 eu fui TypeError: int() argument must be a string, a bytes-like object or a number, not 'filter'- corrigi-lo usandoint("".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]

3
Bem-vindo ao SO e obrigado por postar uma resposta. É sempre uma boa prática adicionar alguns comentários adicionais à sua resposta e por que ela resolve o problema, em vez de apenas postar um trecho de código.
— sebs 29/03

não funcionou no meu caso. não muito diferente da resposta acima
— Oldboy

ValueError: não foi possível converter a string em float: 'e' e, em alguns casos, não funciona :(
— Vilq 6/19

15

Eu estava procurando uma solução para remover as máscaras de strings, especificamente dos números de telefones brasileiros, este post não respondeu, mas me inspirou. Esta é a minha solução:

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

12

Usando Regex abaixo é o caminho

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)

com 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']

Você deve pelo menos compilar o regex se você não estiver usandofindall()
— information_interchange

2
repl_str = re.compile('\d+.?\d*') deve ser: repl_str = re.compile('\d+\.?\d*') Para um exemplo reproduzível 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 10/11/19

8
line2 = "hello 12 hi 89"
temp1 = re.findall(r'\d+', line2) # through regular expression
res2 = list(map(int, temp1))
print(res2)

Oi ,

você pode pesquisar todos os números inteiros na cadeia de caracteres usando dígitos, usando a expressão findall.

Na segunda etapa, crie uma lista res2 e adicione os dígitos encontrados na string a esta lista

espero que isto ajude

Atenciosamente, Diwakar Sharma


A resposta fornecida foi sinalizada para revisão como uma publicação de baixa qualidade. Aqui estão algumas diretrizes para Como redigir uma boa resposta? . Esta resposta fornecida pode estar correta, mas pode se beneficiar de uma explicação. As respostas apenas de código não são consideradas "boas". Da revisão .
— Trenton McKinney

solução simples e funcional, apreciada
— moyo 27/05

7

Esta resposta também contém o caso em que o número é flutuante na string

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)

5

Fico surpreso ao ver que ninguém mencionou ainda o uso de itertools.groupbyuma alternativa para conseguir isso.

Você pode usar itertools.groupby()junto com str.isdigit()para extrair números da string 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]

O valor retido por lserá:

[12, 89]

PS: Isto é apenas para fins ilustrativos, para mostrar que, como alternativa, também poderíamos usar groupbypara conseguir isso. Mas essa não é uma solução recomendada. Se você deseja conseguir isso, deve usar a resposta aceita da fmark com base no uso da compreensão de lista str.isdigitcomo filtro.


4

Estou apenas adicionando esta resposta porque ninguém adicionou uma usando manipulação de exceção e porque isso também funciona para carros alegóricos

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

Resultado :

[1234.0, 56.78]

4

Para capturar padrões diferentes, é útil consultar com padrões diferentes.

Configure todos os padrões que capturam diferentes padrões numéricos de interesse:

(localiza vírgulas) 12.300 ou 12.300,00

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

(encontra flutuadores) 0,123 ou 0,123

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

(localiza números inteiros) 123

'[\ d] +'

Combine com pipe (|) em um padrão com múltiplos ou condicionais .

(Nota: Coloque os padrões complexos em primeiro lugar, caso contrário, os padrões simples retornarão pedaços da captura complexa em vez de a captura complexa retornar a captura completa).

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

Abaixo, confirmaremos a presença de um padrão re.search()e retornaremos uma lista iterável de capturas. Por fim, imprimiremos cada captura usando a notação entre colchetes para selecionar novamente o valor de retorno do objeto de correspondência do objeto de correspondência.

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

Devoluções:

33
42
32
30
444.4
12,001

2

Como nenhum deles lida com números financeiros do mundo real em excel e word docs que eu precisava encontrar, aqui está minha variação. Ele lida com ints, floats, números negativos, números de moeda (porque não responde na divisão) e tem a opção de eliminar a parte decimal e apenas retornar ints ou retornar tudo.

Ele também lida com o sistema de números dos Laks indianos, onde as vírgulas aparecem de maneira irregular, e não a cada três números.

Ele não lida com notação científica ou números negativos colocados entre parênteses nos orçamentos - parecerão positivos.

Também não extrai datas. Existem maneiras melhores de encontrar datas em strings.

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

1

@ jmnas, gostei da sua resposta, mas não encontrou carros alegóricos. Estou trabalhando em um script para analisar o código que vai para uma fábrica CNC e precisava encontrar as dimensões X e Y que podem ser números inteiros ou flutuantes, então adaptei seu código ao seguinte. Isso encontra int, float com valores positivos e negativos. Ainda não encontra valores no formato hexadecimal, mas você pode adicionar "x" e "A" a "F" à num_chartupla e acho que analisaria coisas 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)

0

A melhor opção que encontrei está abaixo. Ele extrairá um número e pode eliminar qualquer tipo de caractere.

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)    

0

Para números de telefone, você pode simplesmente excluir todos os caracteres que não sejam dígitos com \ D no regex:

import re

phone_number = '(619) 459-3635'
phone_number = re.sub(r"\D", "", phone_number)
print(phone_number)
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.