Как извлечь числа из строки в Python?

432

Я бы извлек все числа, содержащиеся в строке. Что лучше подходит для цели, регулярных выражений или isdigit()метода?

Пример:

line = "hello 12 hi 89"

Результат:

[12, 89]
pablouche
источник

Ответы:

485

Если вы хотите извлечь только положительные целые числа, попробуйте следующее:

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

Я бы сказал, что это лучше, чем пример регулярных выражений по трем причинам. Во-первых, вам не нужен другой модуль; во-вторых, он более читабелен, потому что вам не нужно разбирать мини-язык регулярных выражений; и в-третьих, это быстрее (и, следовательно, скорее более питон):

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

Это не будет распознавать числа с плавающей запятой, отрицательные целые числа или целые числа в шестнадцатеричном формате. Если вы не можете принять эти ограничения, ответ Слима ниже поможет.

fmark
источник
5
это не удастся для случая типа «h3110 23 кошка 444.4 кролик 11-2 собака»
sharafjaffri
8
Нормативный случай использования re. Это общий и мощный инструмент (поэтому вы узнаете что-то очень полезное). Скорость несколько не имеет значения при разборе журнала (это не какой-то интенсивный числовой решатель в конце концов), reмодуль находится в стандартной библиотеке Python, и его загрузка не помешает.
Иоаннис Филиппидис
19
У меня были строки, mumblejumble45mumblejumbleв которых я знал, что был только один номер. Решение простое int(filter(str.isdigit, your_string)).
Йонас Линделёв
1
Небольшой комментарий: вы определяете переменную, strкоторая затем переопределяет strобъект и метод в базовом питоне. Это не очень хорошая практика, так как она может понадобиться вам позже в сценарии.
Йонас Линделёв
11
int(filter(...))повысит TypeError: int() argument must be a string...для Python 3.5, так что вы можете использовать обновленную версию: int(''.join(filter(str.isdigit, your_string)))для извлечения всех цифр в одно целое число.
Марк Мишин
449

Я бы использовал регулярное выражение:

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

Это также будет соответствовать 42 от bla42bla. Если вам нужны только числа, разделенные границами слов (пробел, точка, запятая), вы можете использовать \ b:

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

Чтобы получить список чисел вместо списка строк:

>>> [int(s) for s in re.findall(r'\b\d+\b', 'he33llo 42 I\'m a 32 string 30')]
[42, 32, 30]
Винсент Савард
источник
9
... а затем нанесите intна карту и все готово. +1 особенно для последней части. Я бы предложил сырые строки ( r'\b\d+\b' == '\\b\\d+\\b'), хотя.
5
Его можно поместить в список с генератором, например:int_list = [int(s) for s in re.findall('\\d+', 'hello 12 hi 89')]
GreenMatt
7
@GreenMatt: технически это понимание списка (а не генератор), но я бы согласился, что понимание / генераторы более Pythonic, чем map.
Сет Джонсон
1
@Seth Джонсон: Ой! Вы правы, я опечатка в том, что было, по-видимому, в туманном состоянии. :-( Спасибо за исправление!
GreenMatt
2
У меня проблема, хотя. Что делать, если я хочу извлечь числа с плавающей точкой, такие как 1.45 в "hello1.45 hi". Это даст мне 1 и 45 как два разных числа
ab123
89

Это более чем поздно, но вы можете расширить выражение регулярного выражения, чтобы учесть и научную нотацию.

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)

Дает все хорошо!

Кроме того, вы можете посмотреть на регулярное выражение AWS Glue

aidan.plenert.macdonald
источник
1
Поскольку это единственный ответ, который кому-либо нравится, вот как это сделать с помощью научной записи "[- +]? \ D + [\.]? \ D * [Ee]? \ D *". Или какой-то вариант. Радоваться, веселиться!
aidan.plenert.macdonald
Найти есть проблема в простейшем случае, например, не s = "4"возвращает совпадений. Можно ли отредактировать, чтобы позаботиться об этом?
batFINGER
1
хорошо, но он не обрабатывает запятые (например, 74 600)
yekta
Более многословная группа: [+-]?\d*[\.]?\d*(?:(?:[eE])[+-]?\d+)?Эта группа дает некоторые ложные срабатывания (то +есть иногда захватывается сама собой), но может обрабатывать больше форм, например .001, плюс она не объединяет числа автоматически (как в s=2+1)
DavisDude
24
Ах да, очевидное [-+]?[.]?[\d]+(?:,\d\d\d)*[\.]?\d*(?:[eE][-+]?\d+)?- так глупо с моей стороны ... как я мог не думать об этом?
Przemek D
70

Я предполагаю, что вы хотите, чтобы числа с плавающей точкой не просто целые числа, поэтому я бы сделал что-то вроде этого:

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

Обратите внимание, что некоторые другие решения, опубликованные здесь, не работают с отрицательными числами:

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

>>> '-3'.isdigit()
False
jmnas
источник
Это находит положительные и отрицательные числа с плавающей запятой и целые числа. Для положительных и отрицательных целых чисел измените floatна int.
Уго
3
Для отрицательных чисел:re.findall("[-\d]+", "1 -2")
ytpillai
Есть ли какая-то разница, если мы напишем continueвместо passцикла?
Д. Джонс
Это ловит больше, чем просто положительные целые числа, но использование split () пропустит числа с символами валюты, предшествующими первой цифре без пробела, что часто встречается в финансовых документах
Marc Maxmeister
Не работает для чисел с плавающей запятой, в которых нет пробелов с другими символами, например: «4.5 k вещи» будут работать, «4.5k вещи» не будет.
Джей Д.
64

Если вы знаете, что в строке будет только одно число, то есть «привет 12 привет», вы можете попробовать фильтр.

Например:

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

Но будь осторожен !!! :

In [4]: int(''.join(filter(str.isdigit, '200 grams 5')))
Out[4]: 2005
dfostic
источник
12
В Python 3.6.3 я получил TypeError: int() argument must be a string, a bytes-like object or a number, not 'filter'- исправил это с помощьюint("".join(filter(str.isdigit, '200 grams')))
Кент 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
источник
3
Добро пожаловать в ТАК и спасибо за размещение ответа. Хорошей практикой всегда является добавление некоторых дополнительных комментариев к вашему ответу и почему оно решает проблему, а не просто публикация фрагмента кода.
февраля
не работал в моем случае. не сильно отличается от ответа выше
старик
ValueError: не удалось преобразовать строку в число с плавающей точкой: 'e', ​​и в некоторых случаях это не работает :(
Vilq
15

Я искал решение для удаления масок строк, особенно с бразильских номеров телефонов, этот пост не ответил, но вдохновил меня. Это мое решение:

>>> phone_number = '+55(11)8715-9877'
>>> ''.join([n for n in phone_number if n.isdigit()])
'551187159877'
Сидон
источник
12

Использование Regex ниже - это способ

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)

с поиском 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']
сим
источник
Вы должны по крайней мере скомпилировать регулярное выражение, если вы не используетеfindall()
information_interchange
2
repl_str = re.compile('\d+.?\d*') должно быть: repl_str = re.compile('\d+\.?\d*') Для воспроизводимого примера с использованием python3.7 re.search(re.compile(r'\d+.?\d*'), "42G").group() '42G' re.search(re.compile(r'\d+\.?\d*'), "42G").group() '42'
Алексис Лукаттини
8
line2 = "hello 12 hi 89"
temp1 = re.findall(r'\d+', line2) # through regular expression
res2 = list(map(int, temp1))
print(res2)

Привет ,

Вы можете искать все целые числа в строке через цифру, используя выражение findall.

На втором шаге создайте список res2 и добавьте найденные в строке цифры в этот список.

надеюсь это поможет

С уважением, Дивакар Шарма

Дивакар Шарма
источник
Предоставленный ответ был помечен для просмотра как сообщение низкого качества. Вот несколько рекомендаций о том, как мне написать хороший ответ? , Этот предоставленный ответ может быть правильным, но это могло бы извлечь выгоду из объяснения. Ответы только кода не считаются "хорошими" ответами. Из обзора .
Трентон МакКинни
простое и рабочее решение, оценено
моё
7

Этот ответ также содержит случай, когда число находится в строке

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

Я поражен, увидев, что никто еще не упомянул об использовании itertools.groupbyв качестве альтернативы для достижения этой цели.

Вы можете использовать itertools.groupby()наряду с str.isdigit()тем, чтобы извлечь числа из строки как:

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]

Значение удержания lбудет:

[12, 89]

PS: Это просто для иллюстрации, чтобы показать, что в качестве альтернативы мы могли бы также использовать groupbyдля достижения этой цели. Но это не рекомендуемое решение. Если вы хотите добиться этого, вы должны использовать принятый ответ fmark, основанный на использовании понимания списка с использованием str.isdigitфильтра.

Мойнуддин Квадри
источник
4

Я просто добавляю этот ответ, потому что никто не добавил один, использующий обработку исключений, и потому что это также работает с плавающей точкой

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

Вывод :

[1234.0, 56.78]
Raghav
источник
4

Чтобы поймать разные шаблоны, полезно запрашивать разные шаблоны.

Настройте все шаблоны, которые улавливают различные числовые шаблоны:

(находит запятые) 12 300 или 12 300,00

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

(находит поплавки) 0,123 или 0,123

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

(находит целые числа) 123

'[\ D] +'

Объединить с трубой (|) в один шаблон с несколькими или условными .

(Примечание: сначала разместите сложные шаблоны, иначе простые шаблоны будут возвращать фрагменты сложного улова вместо сложного улова, возвращающего полный улов).

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

Ниже мы подтвердим наличие шаблона с re.search(), а затем вернем итеративный список уловов. Наконец, мы распечатаем каждый улов, используя скобочные обозначения, чтобы отобрать возвращаемое значение объекта сопоставления из объекта сопоставления.

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

Возвращает:

33
42
32
30
444.4
12,001
Джеймс Эндрю Буш
источник
2

Поскольку ни один из них не касался реальных финансовых показателей в Excel и документах Word, которые мне нужно было найти, вот мой вариант. Он обрабатывает целые числа, числа с плавающей запятой, отрицательные числа, номера валют (потому что он не отвечает при разделении), и имеет возможность удалить десятичную часть и просто вернуть целые числа или вернуть все.

Он также обрабатывает систему счисления индейцев лаков, где запятые появляются нерегулярно, а не через каждые 3 числа.

Он не обрабатывает научные обозначения или отрицательные числа, заключенные в круглые скобки в бюджетах - будет казаться положительным.

Это также не извлекает даты. Есть лучшие способы поиска дат в строках.

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, мне понравился твой ответ, но он не нашел поплавков. Я работаю над сценарием для анализа кода, идущего на станки с ЧПУ, и мне нужно было найти измерения X и Y, которые могут быть целыми или плавающими, поэтому я адаптировал ваш код к следующему. Это находит int, float с положительными и отрицательными значениями. Все еще не находит значения в шестнадцатеричном формате, но вы можете добавить «x» и «A» к «F» в num_charкортеж, и я думаю, что он будет анализировать такие вещи, как «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
источник
0

Лучший вариант, который я нашел ниже. Он извлечет число и может устранить любой тип символа.

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

Для телефонных номеров вы можете просто исключить все нецифровые символы с \ D в регулярном выражении:

import re

phone_number = '(619) 459-3635'
phone_number = re.sub(r"\D", "", phone_number)
print(phone_number)
Антонин ГАВРЕЛЬ
источник