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]
pablouche
fonte

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.

fmark
fonte
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]
Vincent Savard
fonte
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

aidan.plenert.macdonald
fonte
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
jmnas
fonte
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
dfostic
fonte
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]
AndreiS
fonte
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'
Sidon
fonte
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']
sim
fonte
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

Diwakar SHARMA
fonte
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)
Menglong Li
fonte
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.

Moinuddin Quadri
fonte
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]
Raghav
fonte
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
James Andrew Bush
fonte
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
Marc Maxmeister
fonte
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)
ZacSketches
fonte
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)    
Ajay Kumar
fonte
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)
Antonin GAVREL
fonte