Вопрос: Как проверить, является ли строка числом (float)?


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

Функция, которую я сейчас имею сейчас:

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

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


1225


источник


Ответы:


Который, не только уродливый и медленный

Я бы обманул обоих.

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

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

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

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

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

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


559



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

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

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

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


1311



Есть одно исключение, которое вы можете учесть: строка «NaN»

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

>>> float('NaN')
nan

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

Г.


62



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()

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

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




EDIT: результаты тестов

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


61



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

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

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

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

вернет false

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


51



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

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

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

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

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


37



Updated after Alfe pointed out you don't need to check for float separately as complex handles both:

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

    return True

Previously said: Is some rare cases you might also need to check for complex numbers (e.g. 1+2i), which can not be represented by a float:

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



For int use this:

>>> "1221323".isdigit()
True

But for float we need some tricks ;-). Every float number has one point...

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

Also for negative numbers just add lstrip():

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

And now we get a universal way:

>>> '-12.34'.lstrip('-').replace('.','',1).isdigit()
True
>>> '.-234'.lstrip('-').replace('.','',1).isdigit()
False

17



Just Mimic C#

In C# there are two different functions that handle parsing of scalar values:

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

float.parse():

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

Note: If you're wondering why I changed the exception to a TypeError, here's the documentation.

float.try_parse():

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

Note: You don't want to return the boolean 'False' because that's still a value type. None is better because it indicates failure. Of course, if you want something different you can change the fail parameter to whatever you want.

To extend float to include the 'parse()' and 'try_parse()' you'll need to monkeypatch the 'float' class to add these methods.

If you want respect pre-existing functions the code should be something like:

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

SideNote: I personally prefer to call it Monkey Punching because it feels like I'm abusing the language when I do this but YMMV.

Usage:

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

And the great Sage Pythonas said to the Holy See Sharpisus, "Anything you can do I can do better; I can do anything better than you."


14



For strings of non-numbers, try: except: is actually slower than regular expressions. For strings of valid numbers, regex is slower. So, the appropriate method depends on your input.

If you find that you are in a performance bind, you can use a new third-party module called fastnumbers that provides a function called isfloat. Full disclosure, I am the author. I have included its results in the timings below.


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

As you can see

  • try: except: was fast for numeric input but very slow for an invalid input
  • regex is very efficient when the input is invalid
  • fastnumbers wins in both cases

14