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


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

我目前拥有的功能是:

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

这不仅是丑陋而且缓慢,似乎很笨重。但是我没有找到一个更好的方法因为打电话 float 在主要功能更糟糕。


1251
2017-12-09 20:03


起源


您当前的解决方案有什么问题?它简短,快速且易读。 - Colonel Panic
而且你不必只返回真或假。您可以返回适当修改的值 - 例如,您可以使用此值将非数字放在引号中。 - Thruston
在成功转换的情况下返回float(s)的结果会不会更好?您仍然检查成功(结果为False)并且您实际上已经进行了转换,无论如何您都可能想要转换。 - Jiminion
虽然这个问题比较老,但我只想说这是一种优雅的方式,记录为 EAFP。这可能是解决这类问题的最佳方案。 - thiruvenkadam
别 失败时返回float(s)或None的结果。如果你然后用它 x = float('0.00'); if x: use_float(x); 你现在的代码中有一个错误。 Truthy值是这些函数引发异常而不是返回的原因 None 首先。一个更好的解决方案就是避免使用效用函数并将调用置于浮点数中 try catch 当你想使用它。 - ovangle


答案:


其中,不仅是丑陋而且缓慢

我两个都有争议。

正则表达式或其他字符串解析将更加丑陋和缓慢。

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

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

  • 如果数字有效,则为数字
  • 状态代码(例如,通过errno)或异常以显示无法解析有效数字。

C(作为一个例子)以多种方式解决这个问题。 Python明确而明确地阐述了它。

我认为你这样做的代码是完美的。


566
2017-12-09 20:30



我不认为代码是完美的(但我认为它非常接近):更常见的是 只要 被“测试”的部分 try 条款,所以我会把 return True 在一个 else 的条款 try。其中一个原因是,问题中的代码,如果我不得不审查它,我将不得不检查第二个语句在 try 子句不能引发ValueError:被授予,这不需要太多的时间或脑力,但为什么在不需要时使用任何东西? - Eric Lebigot
答案看起来很引人注目,但让我想知道为什么它没有提供开箱即用......我会复制这个并在任何情况下使用它。 - sage
真可怕。如果我不在乎数字怎么样? 是 只是它是一个数字(这是什么让我在这里)?而不是1行 IsNumeric() 我最终得到了一个try / catch或另一个包装try / catch。啊 - Basic
@Basic我不明白你的观点。命名您的函数,进行检查 IsNumeric 并使用该功能。这就是使用功能的想法 - 拥有单行。 - Nils
它没有提供“开箱即用”,因为 if is_number(s): x = float(x) else: // fail 是与...相同数量的代码行 try: x = float(x) catch TypeError: # fail。这个实用功能是一个完全不必要的抽象。 - ovangle


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

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

字符串方法 -​​ isdigit()

Unicode字符串也有一些东西,我不太熟悉 Unicode - 十进制/十进制


1340
2017-12-09 20:15



但是,这对十六进制不起作用。 - Nico
它也不适用于带小数位数的数字,如1.2 - Daniel Goldberg
这对负面也是负面的 - intrepion
@DanielGoldberg:我认为你需要去查找“数字”的定义 - Jason9987
@ Jason9987,看来你需要重读这个问题。 - Daniel Goldberg


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

如果你想让is_number为'NaN'返回FALSE,那么这段代码将不起作用,因为Python将它转换为不是数字的数字的表示(谈论身份问题):

>>> float('NaN')
nan

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

G。


64
2017-09-01 14:06



其实, NaN 可能是一个很好的回报价值(而不是 False)如果传递的文本实际上不是数字的表示。检查它是一种痛苦(Python的 float 类型确实需要一个方法)但你可以在计算中使用它而不会产生错误,只需要检查结果。 - kindall
另一个例外是字符串 'inf'。或 inf 要么 NaN 也可以加上前缀 + 要么 - 并且仍然被接受。 - agf
如果要为NaN和Inf返回False,请将行更改为x = float(s); return(x == x)和(x - 1!= x)。对于除Inf和NaN之外的所有浮点数,此值应返回True - RyanN
x-1 == x 适用于小于的小浮子 inf。从Python 3.2你可以使用 math.isfinite 测试既不是NaN也不是无限的数字,或者检查两者 math.isnan 和 math.isinf 在那之前。 - Steve Jessop


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

enter image description here


64
2018-05-13 19:28



好的图表+1。我看到了基准测试和锯图,所有TL; DR事情变得清晰直观。 - Julian Chukwu
我同意@JCChuks:图表有助于获得所有TL; DR很快。但我认为TL; DR(如: TL; DR :最好的解决方案是 s.replace('.','',1).isdigit())应该出现在这个anwser的开头。在任何情况下,它应该是被接受的。谢谢! - Simon C.
如果这不合适我很抱歉,但你用什么来生成图表/图表? - Pryftan
只是普通的matplotlib - Sebastian
TLDR具有误导性和虚伪性。 “最佳”与任何性能基准都无关。例如,我通常认为可读性远远超过微优化,因此在为我的上下文确定最佳解决方案时,基准测试几乎没有任何重要性。 TLDR会更准确地说明:“如果按照一小组任意基准测试执行时间进行排名,则效果最佳” - Corey Goldberg


这个怎么样:

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

只有在有'或'的情况下才会返回true。在数字串中。

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

将返回false

编辑:刚看到另一条评论...... 添加一个 .replace(badstuff,'',maxnum_badstuff) 对于其他情况可以做到。如果你传递盐而不是任意调味品(参考:XKCD#974)这样做会很好:P


52
2018-05-25 22:22



然而,这并不能解释负数。 - Michael Barton
或十六进制。 - twasbrillig
或者像指数一样的数字 1.234e56 (也可能写成 +1.234E+56 还有几个变种)。 - Alfe
re.match(r'^[+-]*(0[xbo])?[0-9A-Fa-f]*\.?[0-9A-Fa-f]*(E[+-]*[0-9A-Fa-f]+)$', 'str') 应该更好地确定一个数字(但不是全部,我没有声称)。我不建议使用它,更好地使用Questioner的原始代码。 - Baldrickk
如果你不喜欢这个解决方案,请阅读 这个 在downvoting之前! - aloisdg


Alfe指出您不需要单独检查浮动,因为复杂处理两者:

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

38
2017-12-11 04:56



我不同意。在正常使用中这是非常不可能的,并且你最好在使用它们的时候建立一个is_complex_number()调用,而不是用额外的操作加重调用,以便有0.0001%的误操作机会。 - Jiminion
你可以去除 float() 东西完全,只是检查 complex() 呼吁成功。一切都解析了 float() 可以解析 complex()。 - Alfe
此函数将Pandas的NaNs和Inf值作为数值返回。 - fixxxer


这不仅是丑陋而且缓慢,似乎很笨重。

这可能需要一些时间来习惯,但这是做到这一点的pythonic方式。正如已经指出的那样,替代方案更糟糕。但是以这种方式做事还有另一个好处:多态性。

鸭子打字背后的核心理念是“如果它像鸭子那样走路和说话,那么它就是鸭子。”如果您决定需要子类化字符串以便您可以更改确定是否可以将某些内容转换为浮点数的方式,该怎么办?或者如果您决定完全测试其他对象怎么办?您无需更改上述代码即可完成这些操作。

其他语言通过使用接口解决了这些问题。我将保存分析哪个解决方案更适合另一个线程。但问题是,python肯定是在等式的鸭子打字方面,如果你打算用Python做很多编程,你可能不得不习惯这样的语法(但这并不意味着你必须喜欢它当然)。

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


37
2017-09-08 08:42



Python使用异常进行基本功能的另一个常见地方是 hasattr() 这只是一个 getattr() 呼叫包裹在一个 try/except。尽管如此,异常处理比正常的流控制要慢,所以将它用于一些真实的事情 大多数时候 会导致性能下降。 - kindall
似乎如果你想要一个单行,你就是SOL - Basic
对于具有廉价例外的影响,pythonic也是“更好地请求宽恕而不是许可”的想法。 - heltonbiker


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

18
2018-02-18 01:35



不处理像这样的事情 1.234e56 和类似的。另外,我会对你如何发现它感兴趣 99999999999999999999e99999999999999999999 不是一个数字。试图解析它很快发现。 - Alfe
这比50m字符串列表中的接受解决方案快约30%,并且在5k字符串列表上快150%。 - Zev Averbach


只是模仿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()',你需要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

伟大的Sage Pythonas对教廷Sharpisus说:“你能做的任何事情我都能做得更好;我能做比你更好的事情。”


14
2017-08-14 03:34



我最近在大多数JS编码,并没有实际测试这,所以可能会有一些小错误。如果你看到任何,请随时纠正我的错误。 - Evan Plaice
要添加对复数的支持,请参阅@Matthew Wilcoxson的答案。 stackoverflow.com/a/3335060/290340。 - Evan Plaice
运用 ! 代替 not 可能是一个小错误,但您绝对无法为内置指定属性 float 在CPython中。 - BlackJack


对于非数字字符串, 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 两种情况都胜出

14
2018-01-05 15:21



我的立场得到了纠正: - }它看起来并不像是在做这件事。也许使用像 prep_code_basis 和 prep_code_re_method 本来可以防止我的错误。 - Alfe
你介意解释你的模块是如何工作的,至少对于 isfloat 功能? - Solomon Ucko
@SolomonUcko以下是字符串检查部分源代码的链接: github.com/SethMMorton/fastnumbers/blob/v1.0.0/src/...。基本上,它按顺序遍历字符串中的每个字符,并验证它是否遵循有效浮点的模式。如果输入已经是一个数字,它只使用快速 PyFloat_Check。 - SethMMorton