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.