Come estrarre numeri da una stringa in Python?


432

Vorrei estrarre tutti i numeri contenuti in una stringa. Qual è il più adatto allo scopo, alle espressioni regolari o al isdigit()metodo?

Esempio:

line = "hello 12 hi 89"

Risultato:

[12, 89]

Risposte:


485

Se desideri estrarre solo numeri interi positivi, prova quanto segue:

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

Direi che questo è meglio dell'esempio regex per tre ragioni. Innanzitutto, non è necessario un altro modulo; secondo, è più leggibile perché non è necessario analizzare il mini-linguaggio regex; e terzo, è più veloce (e quindi probabilmente più pitonico):

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

Ciò non riconoscerà float, numeri interi negativi o numeri interi in formato esadecimale. Se non riesci ad accettare queste limitazioni, la risposta di slim qui sotto farà il trucco.


5
questo fallirà per casi come "h3110 23 cat 444.4 rabbit 11-2 dog"
sharafjaffri

8
Il caso normativo sta usando re. È uno strumento generale e potente (quindi impari qualcosa di molto utile). La velocità è in qualche modo irrilevante nell'analisi dei log (non è un risolutore numerico intenso dopo tutto), il remodulo si trova nella libreria Python standard e non fa male caricarlo.
Ioannis Filippidis,

19
Avevo delle stringhe come quelle mumblejumble45mumblejumblein cui sapevo che c'era un solo numero. La soluzione è semplicemente int(filter(str.isdigit, your_string)).
Jonas Lindeløv,

1
Un commento secondario: si definisce la variabile strche quindi sovrascrive l' stroggetto e il metodo in Python di base. Non è una buona pratica poiché potresti averne bisogno più avanti nella sceneggiatura.
Jonas Lindeløv,

11
int(filter(...))rilancierà TypeError: int() argument must be a string...per Python 3.5, quindi puoi usare la versione aggiornata: int(''.join(filter(str.isdigit, your_string)))per estrarre tutte le cifre in un intero.
Mark Mishyn il

449

Vorrei usare un regexp:

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

Ciò corrisponderebbe anche a 42 da bla42bla. Se vuoi solo numeri delimitati da limiti di parole (spazio, punto, virgola), puoi usare \ b:

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

Per finire con un elenco di numeri anziché un elenco di stringhe:

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

9
... e poi mappalo intsu di esso e il gioco è fatto. +1 soprattutto per l'ultima parte. Suggerirei stringhe grezze ( r'\b\d+\b' == '\\b\\d+\\b') però.

5
Potrebbe essere inserito in un elenco con un generatore, ad esempio:int_list = [int(s) for s in re.findall('\\d+', 'hello 12 hi 89')]
GreenMatt,

7
@GreenMatt: questa è tecnicamente una comprensione dell'elenco (non un generatore), ma concordo sul fatto che le comprensioni / i generatori sono più Pythonic di map.
Seth Johnson,

1
@Seth Johnson: Oops! Hai ragione, ho sbagliato a scrivere in quello che apparentemente era uno stato mentale annebbiato. :-( Grazie per la correzione!
GreenMatt,

2
Ho un problema però. E se volessi estrarre numeri float come anche 1.45 in "hello1.45 hi". Mi darà 1 e 45 come due numeri diversi
ab123

89

Questo è più che un po 'in ritardo, ma puoi estendere l'espressione regex per tenere conto anche della notazione scientifica.

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à tutto bene!

Inoltre, puoi guardare la regex integrata di AWS Glue


1
Poiché questa è l'unica risposta che piace a qualcuno, ecco come farlo con la notazione scientifica "[- +]? \ D + [\.]? \ D * [Ee]? \ D *". O qualche variazione. Divertiti!
aidan.plenert.macdonald,

Scopri che c'è un problema con il caso più semplice, ad es. s = "4"Non restituisce corrispondenze. Può essere modificato per occuparsi anche di questo?
batFINGER,

1
bello ma non gestisce le virgole (es. 74.600)
yekta,

Un gruppo più dettagliato è [+-]?\d*[\.]?\d*(?:(?:[eE])[+-]?\d+)?Questo gruppo fornisce alcuni falsi positivi (cioè +viene catturato da solo a volte), ma è in grado di gestire più forme, come .001, inoltre, non combina automaticamente i numeri (come in s=2+1)
DavisDude

24
Ah sì, l'ovvio [-+]?[.]?[\d]+(?:,\d\d\d)*[\.]?\d*(?:[eE][-+]?\d+)?- così sciocco da parte mia ... come potrei non pensarci?
Przemek D,

70

Suppongo che tu voglia float non solo numeri interi, quindi farei qualcosa del genere:

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

Nota che alcune delle altre soluzioni pubblicate qui non funzionano con numeri negativi:

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

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

Questo trova numeri e float positivi e negativi. Per solo numeri interi positivi e negativi, floatpassa a int.
Hugo,

3
Per i numeri negativi:re.findall("[-\d]+", "1 -2")
ytpillai,

Fa differenza se scriviamo continueinvece che passnel ciclo?
D. Jones,

Questo cattura più di interi interi positivi, ma usando split () mancheranno numeri che hanno simboli di valuta che precedono la prima cifra senza spazio, cosa comune nei documenti finanziari
Marc Maxmeister,

Non funziona per i float che non hanno spazio con altri personaggi, ad esempio: "4.5 k cose" funzionerà, "4.5k cose" no.
Jay D.

64

Se sai che nella stringa sarà presente un solo numero, ad esempio "ciao 12 ciao", puoi provare a filtrare.

Per esempio:

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

Ma stai attento !!! :

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

12
In Python 3.6.3 ho ottenuto TypeError: int() argument must be a string, a bytes-like object or a number, not 'filter'- risolto usandoint("".join(filter(str.isdigit, '200 grams')))
Kent Munthe Caspersen il

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
Benvenuti in SO e grazie per aver inviato una risposta. È sempre buona norma aggiungere altri commenti alla tua risposta e perché risolve il problema, piuttosto che pubblicare solo uno snippet di codice.
settembre

non ha funzionato nel mio caso. non molto diverso dalla risposta sopra
oldboy

ValueError: impossibile convertire la stringa in float: 'e' e in alcuni casi non funziona :(
Vilq

15

Stavo cercando una soluzione per rimuovere le maschere delle stringhe, in particolare dai numeri di telefoni brasiliani, questo post non ha risposto ma mi ha ispirato. Questa è la mia soluzione:

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

12

Utilizzando Regex di seguito è il modo

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

Dovresti almeno compilare il regex se non lo stai usandofindall()
information_interchange il

2
repl_str = re.compile('\d+.?\d*') dovrebbe essere: repl_str = re.compile('\d+\.?\d*') Per un esempio riproducibile 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)

Ciao ,

puoi cercare tutti i numeri interi nella stringa tramite cifra usando l'espressione findall.

Nel secondo passaggio creare un elenco res2 e aggiungere le cifre trovate nella stringa a questo elenco

spero che sia di aiuto

Saluti, Diwakar Sharma


La risposta fornita è stata contrassegnata per la revisione come post di bassa qualità. Ecco alcune linee guida per Come posso scrivere una buona risposta? . Questa risposta fornita potrebbe essere corretta, ma potrebbe trarre vantaggio da una spiegazione. Le risposte solo al codice non sono considerate risposte "buone". Dalla recensione .
Trenton McKinney,

soluzione semplice e funzionante, apprezzata
moyo

7

Questa risposta contiene anche il caso in cui il numero è mobile nella stringa

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

Sono sorpreso di vedere che nessuno ha ancora menzionato l'uso di itertools.groupbyun'alternativa per raggiungere questo obiettivo.

È possibile utilizzare itertools.groupby()insieme str.isdigit()per estrarre i numeri dalla stringa come:

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]

Il valore trattenuto lsarà:

[12, 89]

PS: Questo è solo a scopo illustrativo per mostrare che come alternativa potremmo anche usare groupbyper raggiungere questo obiettivo. Ma questa non è una soluzione raccomandata. Se vuoi raggiungere questo obiettivo, dovresti utilizzare la risposta accettata di fmark in base all'uso della comprensione dell'elenco con str.isdigitcome filtro.


4

Sto solo aggiungendo questa risposta perché nessuno ha aggiunto nessuno usando la gestione delle eccezioni e perché questo funziona anche per i float

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

Produzione :

[1234.0, 56.78]

4

Per acquisire modelli diversi è utile eseguire query con modelli diversi.

Imposta tutti gli schemi che catturano diversi schemi numerici di interesse:

(trova virgole) 12.300 o 12.300,00

'[\ D] + [., \ D] +'

(trova float) 0.123 o .123

'[\ D] * [.] [\ D] +'

(trova numeri interi) 123

'[\ D] +'

Combina con pipe (|) in un modello con multipli o condizionali .

(Nota: metti prima i modelli complessi altrimenti i modelli semplici restituiranno blocchi della cattura complessa anziché la cattura complessa che restituisce la cattura completa).

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

Di seguito, confermeremo che è presente un modello re.search(), quindi restituiamo un elenco iterabile di catture. Infine, stamperemo ogni cattura usando la notazione parentesi per selezionare il valore restituito dell'oggetto match dall'oggetto match.

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

Ritorna:

33
42
32
30
444.4
12,001

2

Dal momento che nessuno di questi si occupava di numeri finanziari del mondo reale in documenti di Excel e word che dovevo trovare, ecco la mia variazione. Gestisce ints, float, numeri negativi, numeri di valuta (perché non risponde alla divisione) e ha la possibilità di eliminare la parte decimale e restituire solo ints o restituire tutto.

Gestisce anche il sistema di numeri dei Laks indiani in cui le virgole appaiono in modo irregolare, non ogni 3 numeri a parte.

Non gestisce la notazione scientifica o i numeri negativi inseriti tra parentesi nei budget - sembreranno positivi.

Inoltre non estrae le date. Esistono modi migliori per trovare le date nelle stringhe.

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, mi è piaciuta la tua risposta, ma non ha trovato float. Sto lavorando a uno script per analizzare il codice che va in un mulino a controllo numerico e dovevo trovare le dimensioni X e Y che possono essere numeri interi o float, quindi ho adattato il tuo codice al seguente. Questo trova int, float con val positivi e negativi. Non trova ancora valori in formato esadecimale ma potresti aggiungere "x" e "A" a "F" alla num_chartupla e penso che analizzerebbe cose come '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

L'opzione migliore che ho trovato è di seguito. Estrarrà un numero e può eliminare qualsiasi tipo di carattere.

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

Per i numeri di telefono puoi semplicemente escludere tutti i caratteri non digitati con \ D in regex:

import re

phone_number = '(619) 459-3635'
phone_number = re.sub(r"\D", "", phone_number)
print(phone_number)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.