如何检查字符串是否为数字(浮点数)?

检查字符串是否可以在 Python 中表示为数字的最佳方法是什么?

我目前拥有的功能是:

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

不仅丑陋且缓慢,而且看起来笨拙。但是我还没有找到更好的方法,因为在 main 函数中调用float更加糟糕。

答案

如果您正在寻找解析(正,无符号)整数而不是浮点数,则可以将isdigit()函数用于字符串对象。

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

字符串方法-isdigit isdigit()Python2Python3

Unicode 字符串上也有一些内容,我不太熟悉Unicode - 是十进制 / 十进制

不仅丑陋而且缓慢

我都对此表示怀疑。

正则表达式或其他字符串解析方法将更难看,更慢。

我不确定任何事情都可以比上述速度更快。它调用该函数并返回。 Try / Catch 不会带来太多的开销,因为最常见的异常是在没有大量搜索堆栈帧的情况下捕获的。

问题是任何数值转换函数都有两种结果

  • 一个数字(如果该数字有效)
  • 状态代码(例如,通过 errno)或异常,表明无法解析任何有效数字。

C(作为示例)通过多种方式进行破解。 Python 清楚明确地列出了它。

我认为您执行此操作的代码是完美的。

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-block 很慢。但更重要的是,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

编辑:基准结果

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

在此处输入图片说明

您可能需要考虑一个例外:字符串 “NaN”

如果您希望 is_number 为'NaN' 返回 FALSE,则此代码将不起作用,因为 Python 会将其转换为非数字的表示形式(谈论身份问题):

>>> float('NaN')
nan

否则,我实际上应该感谢您现在广泛使用的那段代码。 :)

G。

这个怎么样:

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

仅当存在一个或不存在 “。” 时,它才返回 true。在数字字符串中。

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

将返回假

编辑:刚刚看到另一条评论... 可以为其他情况添加.replace(badstuff,'',maxnum_badstuff) 。如果您传递盐而不是任意调味品(ref: xkcd#974 ),这将很好:P

在 Alfe 指出您不需要单独检查 float 之后进行了更新,因为这两种情况都比较复杂:

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

不仅丑陋且缓慢,而且看起来笨拙。

这可能需要一些时间来适应,但这是实现此目的的 Python 方法。正如已经指出的那样,替代方案更糟。但是用这种方式做事还有另一个优点:多态。

鸭打字背后的中心思想是 “如果它像鸭子一样走路和说话,那就是鸭子。” 如果您决定需要对字符串进行子类化,以便可以更改确定将某些内容转换为浮点数的方式,该怎么办?或者,如果您决定完全测试其他对象,该怎么办?您可以执行这些操作,而不必更改上面的代码。

其他语言通过使用接口来解决这些问题。我将保存对另一线程更好的解决方案的分析。不过,要点是,Python 绝对位于等式的鸭式输入端,如果您打算在 Python 中进行大量编程,则可能必须习惯使用这种语法(但这并不意味着您当然要喜欢它)。

您可能还需要考虑的另一件事:与许多其他语言相比,Python 在引发和捕获异常方面非常快(例如,比. Net 快 30 倍)。哎呀,语言本身甚至抛出异常来传达非异常的正常程序条件(每次使用 for 循环时)。因此,在您注意到一个重大问题之前,我不必担心此代码的性能方面。

对于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

只是模仿 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”,因为它仍然是值类型。没有哪个更好,因为它表示失败。当然,如果您想要不同的东西,可以将 fail 参数更改为所需的任何参数。

要扩展 float 以包括'parse()' 和'try_parse()',您需要对'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

伟大的贤者蟒蛇对圣座夏普西修斯说:“任何你能做的我都能做得更好;我能做的比你做得更好。”

对于非数字字符串,请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获胜