Как проверить, является ли строка числом (с плавающей запятой)? - PullRequest
1418 голосов
/ 09 декабря 2008

Каков наилучший способ проверить, может ли строка представляться как число в Python?

У меня сейчас есть функция:

def is_number(s):
    try:
        float(s)
        return True
    except ValueError:
        return False

Что не только уродливо и медленно, но кажется неуклюжим. Однако я не нашел лучшего метода, потому что вызов float в основной функции еще хуже.

Ответы [ 33 ]

1452 голосов
/ 09 декабря 2008

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

>>> a = "03523"
>>> a.isdigit()
True
>>> b = "963spam"
>>> b.isdigit()
False

Строковые методы - isdigit()

Есть также что-то в строках Unicode, с которыми я не слишком знаком Unicode - десятичный / десятичный

628 голосов
/ 09 декабря 2008

Что не только уродливо и медленно

Я бы оспаривал оба.

Регулярное выражение или другой метод синтаксического анализа строк будет более уродливым и медленным.

Я не уверен, что все может быть быстрее, чем выше. Вызывает функцию и возвращает. Try / Catch не создает больших накладных расходов, поскольку наиболее распространенное исключение перехватывается без расширенного поиска кадров стека.

Проблема в том, что любая функция преобразования чисел имеет два вида результатов

  • Число, если оно действительно
  • Код состояния (например, через errno) или исключение, показывающее, что невозможно проанализировать действительный номер.

C (в качестве примера) взламывает это несколькими способами. Python излагает это ясно и явно.

Я думаю, ваш код для этого идеален.

109 голосов
/ 13 мая 2014

TL; DR Лучшее решение - s.replace('.','',1).isdigit()

Я провел несколько тестов , сравнивая различные подходы

def is_number_tryexcept(s):
    """ Returns True is string is a number. """
    try:
        float(s)
        return True
    except ValueError:
        return False

import re    
def is_number_regex(s):
    """ Returns True is string is a number. """
    if re.match("^\d+?\.\d+?$", s) is None:
        return s.isdigit()
    return True


def is_number_repl_isdigit(s):
    """ Returns True is string is a number. """
    return s.replace('.','',1).isdigit()

Если строка не является числом, блок исключений работает довольно медленно. Но, что более важно, метод try-исключения является единственным подходом, который корректно обрабатывает научные записи.

funcs = [
          is_number_tryexcept, 
          is_number_regex,
          is_number_repl_isdigit
          ]

a_float = '.1234'

print('Float notation ".1234" is not supported by:')
for f in funcs:
    if not f(a_float):
        print('\t -', f.__name__)

Плавающая запись ".1234" не поддерживается:
- is_number_regex

scientific1 = '1.000000e+50'
scientific2 = '1e50'


print('Scientific notation "1.000000e+50" is not supported by:')
for f in funcs:
    if not f(scientific1):
        print('\t -', f.__name__)




print('Scientific notation "1e50" is not supported by:')
for f in funcs:
    if not f(scientific2):
        print('\t -', f.__name__)

Научная запись "1.000000e + 50" не поддерживается:
- is_number_regex
- is_number_repl_isdigit
Научная запись "1e50" не поддерживается:
- is_number_regex
- is_number_repl_isdigit

РЕДАКТИРОВАТЬ: результаты теста

import timeit

test_cases = ['1.12345', '1.12.345', 'abc12345', '12345']
times_n = {f.__name__:[] for f in funcs}

for t in test_cases:
    for f in funcs:
        f = f.__name__
        times_n[f].append(min(timeit.Timer('%s(t)' %f, 
                      'from __main__ import %s, t' %f)
                              .repeat(repeat=3, number=1000000)))

где были протестированы следующие функции

from re import match as re_match
from re import compile as re_compile

def is_number_tryexcept(s):
    """ Returns True is string is a number. """
    try:
        float(s)
        return True
    except ValueError:
        return False

def is_number_regex(s):
    """ Returns True is string is a number. """
    if re_match("^\d+?\.\d+?$", s) is None:
        return s.isdigit()
    return True


comp = re_compile("^\d+?\.\d+?$")    

def compiled_regex(s):
    """ Returns True is string is a number. """
    if comp.match(s) is None:
        return s.isdigit()
    return True


def is_number_repl_isdigit(s):
    """ Returns True is string is a number. """
    return s.replace('.','',1).isdigit()

enter image description here

66 голосов
/ 01 сентября 2010

Существует одно исключение, которое вы можете принять во внимание: строка 'NaN'

Если вы хотите, чтобы is_number возвращал FALSE для 'NaN', этот код не будет работать, поскольку Python преобразует его в представление числа, которое не является числом (поговорим о проблемах идентификации):

>>> float('NaN')
nan

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

G.

53 голосов
/ 26 мая 2012

как насчет этого:

'3.14'.replace('.','',1).isdigit()

, который вернет true, только если есть один или нет '.' в строке цифр.

'3.14.5'.replace('.','',1).isdigit()

вернет false

edit: только что увидел другой комментарий ... добавление .replace(badstuff,'',maxnum_badstuff) для других случаев может быть сделано. если вы передаете соль, а не произвольные приправы (ref: xkcd # 974 ), это подойдет: P

39 голосов
/ 26 июля 2010

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

def is_number(s):
    try:
        complex(s) # for int, long, float and complex
    except ValueError:
        return False

    return True

Ранее говорилось: в некоторых редких случаях вам также может понадобиться проверить комплексные числа (например, 1 + 2i), которые не могут быть представлены с плавающей точкой:

def is_number(s):
    try:
        float(s) # for int, long and float
    except ValueError:
        try:
            complex(s) # for complex
        except ValueError:
            return False

    return True
37 голосов
/ 11 декабря 2008

Что не только уродливо и медленно, но кажется неуклюжим.

Может потребоваться некоторое привыкание, но это питонский способ сделать это. Как уже указывалось, альтернативы хуже. Но есть еще одно преимущество, заключающееся в том, чтобы делать вещи таким образом: полиморфизм.

Основная идея печатания утки заключается в том, что «если она ходит и говорит как утка, то это утка». Что, если вы решите, что вам нужно разбить строку на подклассы, чтобы вы могли изменить способ определения, можно ли что-то преобразовать в число с плавающей точкой? Или что, если вы решите полностью протестировать какой-то другой объект? Вы можете делать это без изменения вышеуказанного кода.

Другие языки решают эти проблемы с помощью интерфейсов. Я сохраню анализ того, какое решение лучше для другого потока. Дело, однако, в том, что python решительно относится к типу уравнения утки, и вам, вероятно, придется привыкнуть к синтаксису, подобному этому, если вы планируете много программировать на Python (но это не значит, что тебе, конечно, это нравится).

Еще одна вещь, которую вы можете принять во внимание: Python довольно быстро генерирует и перехватывает исключения по сравнению со многими другими языками (например, в 30 раз быстрее, чем .Net). Черт, сам язык даже генерирует исключения, чтобы сообщать о неисключительных, нормальных условиях программы (каждый раз, когда вы используете цикл for). Таким образом, я бы не стал сильно беспокоиться об аспектах производительности этого кода, пока вы не заметите существенную проблему.

22 голосов
/ 08 сентября 2015

Для int используйте это:

>>> "1221323".isdigit()
True

Но для float нам нужны некоторые хитрости ;-). Каждый номер с плавающей точкой имеет одну точку ...

>>> "12.34".isdigit()
False
>>> "12.34".replace('.','',1).isdigit()
True
>>> "12.3.4".replace('.','',1).isdigit()
False

Также для отрицательных чисел просто добавьте lstrip():

>>> '-12'.lstrip('-')
'12'

А теперь мы получаем универсальный способ:

>>> '-12.34'.lstrip('-').replace('.','',1).isdigit()
True
>>> '.-234'.lstrip('-').replace('.','',1).isdigit()
False
15 голосов
/ 18 февраля 2012

Just Mimic C #

В C # есть две разные функции, которые обрабатывают разбор скалярных значений:

  • Float.Parse ()
  • Float.TryParse ()

float.parse ():

def parse(string):
    try:
        return float(string)
    except Exception:
        throw TypeError

Примечание. Если вам интересно, почему я заменил исключение на TypeError, вот документация .

float.try_parse ():

def try_parse(string, fail=None):
    try:
        return float(string)
    except Exception:
        return fail;

Примечание. Вы не хотите возвращать логическое значение «False», поскольку это все еще тип значения. Нет лучше, потому что это указывает на неудачу. Конечно, если вы хотите что-то другое, вы можете изменить параметр сбоя на любой другой.

Чтобы расширить float для включения 'parse ()' и 'try_parse ()', вам нужно monkeypatch класс 'float', чтобы добавить эти методы.

Если вы хотите соблюдать существующие функции, код должен выглядеть примерно так:

def monkey_patch():
    if(!hasattr(float, 'parse')):
        float.parse = parse
    if(!hasattr(float, 'try_parse')):
        float.try_parse = try_parse

SideNote: Я лично предпочитаю называть это Monkey Punching, потому что мне кажется, что я злоупотребляю языком, когда делаю это, но YMMV.

Использование:

float.parse('giggity') // throws TypeException
float.parse('54.3') // returns the scalar value 54.3
float.tryParse('twank') // returns None
float.tryParse('32.2') // returns the scalar value 32.2

И великий мудрец Питон сказал Святейшему Престолу Шарпису: «Все, что ты можешь сделать, я могу делать лучше; я могу делать все, что лучше тебя».

15 голосов
/ 14 августа 2014

Для строк, не являющихся числами, try: except: на самом деле медленнее, чем регулярные выражения. Для строк действительных чисел регулярное выражение медленнее. Таким образом, соответствующий метод зависит от вашего ввода.

Если вы обнаружили, что находитесь в привязке к производительности, вы можете использовать новый сторонний модуль с именем fastnumbers , который предоставляет функцию с именем isfloat . Полное раскрытие, я автор. Я включил его результаты в сроки ниже.


from __future__ import print_function
import timeit

prep_base = '''\
x = 'invalid'
y = '5402'
z = '4.754e3'
'''

prep_try_method = '''\
def is_number_try(val):
    try:
        float(val)
        return True
    except ValueError:
        return False

'''

prep_re_method = '''\
import re
float_match = re.compile(r'[-+]?\d*\.?\d+(?:[eE][-+]?\d+)?$').match
def is_number_re(val):
    return bool(float_match(val))

'''

fn_method = '''\
from fastnumbers import isfloat

'''

print('Try with non-number strings', timeit.timeit('is_number_try(x)',
    prep_base + prep_try_method), 'seconds')
print('Try with integer strings', timeit.timeit('is_number_try(y)',
    prep_base + prep_try_method), 'seconds')
print('Try with float strings', timeit.timeit('is_number_try(z)',
    prep_base + prep_try_method), 'seconds')
print()
print('Regex with non-number strings', timeit.timeit('is_number_re(x)',
    prep_base + prep_re_method), 'seconds')
print('Regex with integer strings', timeit.timeit('is_number_re(y)',
    prep_base + prep_re_method), 'seconds')
print('Regex with float strings', timeit.timeit('is_number_re(z)',
    prep_base + prep_re_method), 'seconds')
print()
print('fastnumbers with non-number strings', timeit.timeit('isfloat(x)',
    prep_base + 'from fastnumbers import isfloat'), 'seconds')
print('fastnumbers with integer strings', timeit.timeit('isfloat(y)',
    prep_base + 'from fastnumbers import isfloat'), 'seconds')
print('fastnumbers with float strings', timeit.timeit('isfloat(z)',
    prep_base + 'from fastnumbers import isfloat'), 'seconds')
print()

Try with non-number strings 2.39108395576 seconds
Try with integer strings 0.375686168671 seconds
Try with float strings 0.369210958481 seconds

Regex with non-number strings 0.748660802841 seconds
Regex with integer strings 1.02021503448 seconds
Regex with float strings 1.08564686775 seconds

fastnumbers with non-number strings 0.174362897873 seconds
fastnumbers with integer strings 0.179651021957 seconds
fastnumbers with float strings 0.20222902298 seconds

Как видите

  • try: except: был быстрым для числового ввода, но очень медленным для неправильного ввода
  • регулярное выражение очень эффективно, когда ввод неверен
  • fastnumbers выигрывает в обоих случаях
...