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.