如何检查一个字符串是否表示一个数字(浮点数或整数)?

1961

98
你现有的解决方案有什么问题吗?它很简短、快速且易读。 - Colonel Panic
5
你不仅仅可以返回True或False,你还可以适当修改返回的值。例如,你可以将非数字用引号括起来。 - Thruston
8
如果成功转换,返回float(s)的结果岂不更好?你仍然需要检查是否成功(结果为False),而且你已经完成了转换,这也是你可能想要的。 - Jiminion
10
尽管这个问题比较旧,但我想说这种方法被称为EAFP,是一种优雅的方式。因此,这可能是解决这种问题的最佳方案。 - thiruvenkadam
9
如果转换失败,不要返回float(s)的结果或None。如果你这样使用它x = float('0.00'); if x: use_float(x);,你的代码中现在有一个bug。这些函数抛出异常而不是一开始返回None的原因是为了避免Truthy值。更好的解决方案是在需要使用时避免实用程序函数,而是在调用float时使用try catch语句块捕获异常。 - ovangle
显示剩余16条评论
41个回答

1768

仅针对非负(无符号)整数,请使用isdigit()

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

isdigit()的文档:Python2Python3

对于Python 2中的Unicode字符串: isnumeric()


254
不可以使用否定词。 - intrepion
34
指数形式也不行:“1e3”.isdigit() --> False。 - ssc
39
当Number!= Digit时,寻找一种测试字符串中是否包含整数的方法的人很可能会偶然发现这个问题,而isDigit方法很可能非常适合他们的应用。 - Adam Parkin
14
isdigit()int()对于什么是整数有不同的看法。例如,对于Unicode字符u'\u00b9'u'¹'.isdigit()True,但int(u'¹')会引发ValueError错误。 - jfs
9
+1:isdigit() 可能不是提问者想要的,但却正是我需要的。虽然这个回答和方法可能并不涵盖所有类型的数字,但它仍然非常相关,与其准确性的争论相反。虽然“数字!= 数字字符”,但数字字符仍然是数字的子集,特别是对于那些是正数、非负数以及使用 1-10 基数的数字。此外,对于想要检查一个字符串是否为数字 ID 的情况来说,这种方法特别有用和简洁,而这种情况通常属于我刚才所描述的数字子集。 - Justin Johnson
显示剩余13条评论

778

不仅丑陋而且慢

我对这两点都有异议。

使用正则表达式或其他字符串解析方法会更丑陋和更慢。

我不确定有什么比上述方法更快的了。它调用函数并返回结果。Try/Catch 并不会引入太多额外开销,因为最常见的异常可以在不搜索堆栈帧的情况下捕获。

问题在于任何数值转换函数都有两种结果:

  • 如果数字有效,则返回一个数字;
  • 如果没有解析出有效数字,则返回状态码(例如通过 errno)或异常。

C 语言(作为一个例子)通过一些方式规避了这个问题。Python 明确而明确地展示了它。

我认为你的代码做得很好。


31
我认为代码并不完美(但我认为它非常接近完美):更常见的做法是只将正在“测试”的部分放在 try 语句中,因此我会将 return True 放在 tryelse 语句中。原因之一是,对于问题中的代码,如果我需要审核它,我必须检查 try 语句中的第二个语句是否可能引发 ValueError:虽然这不需要太多时间或脑力,但既然不需要使用任何工具,为什么还要浪费呢? - Eric O. Lebigot
5
这个答案看起来很有说服力,但让我想知道为什么它不是直接提供的…无论如何我会复制并使用它。 - sage
17
好的。如果我不关心这个数字是多少,只关心它是否为数字(这也是我来到这里的原因),怎么办?如果我使用一行的 IsNumeric() 方法,结果却是要么使用 try/catch 语句,要么使用另一个包装了 try/catch 的方法。唉。 - Basic
6
它不是“开箱即用”的,因为“if is_number(s): x = float(x) else: // fail”与“try: x = float(x) catch TypeError: # fail”具有相同数量的代码行。这个实用函数是完全不必要的抽象。 - ovangle
21
但是抽象化是库的核心。拥有一个'isNumber'函数(在任何编程语言中)非常有帮助,因为你可以将它直接构建到if语句中,使代码更容易阅读和维护,而不依赖于try-catch块。此外,如果你需要在多个类/模块中多次使用该代码,则使用自定义函数可能会比内置函数使用更多行代码。 - JamEngulfer
显示剩余7条评论

318

TL;DR 最佳解决方案是s.replace('.','',1).isdigit()

我做了一些比较不同方法的基准测试

def is_number_tryexcept(s):
    """ Returns True if string is a number. """
    try:
        float(s)
        return True
    except ValueError:
        return False
       
import re    
def is_number_regex(s):
    """ Returns True if 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 if 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('科学计数法“1.000000e+50”不被以下工具包支持:') for f in funcs: if not f(scientific1): print('\t -', f.name)

    print('科学计数法“1e50”不被以下工具包支持:') 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 if string is a number. """
    try:
        float(s)
        return True
    except ValueError:
        return False

def is_number_regex(s):
    """ Returns True if 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 if string is a number. """
    if comp.match(s) is None:
        return s.isdigit()
    return True


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

enter image description here


32
对于漂亮的图表加分。我看到了基准测试并看到了图表,所有简短易读的东西都变得清晰而直观。 - jcchuks
15
这种方法不能处理负数(带有减号)。我建议使用float方法,因为它更不容易出错,并且每次都有效。 - Urchin
8
需要注意的是,即使假设不能有破折号,replace-isdigit方法只对非数字(False结果)更快,而try-except方法对数字(True结果)更快。如果大部分输入都是有效输入,则最好使用try-except解决方案! - Markus von Broady
4
无法处理指数形式(例如 '1.5e-9')或负数。 - EL_DON
7
除去指数和负数的明显警告外,这个方法非常优秀,你可以通过串联s.replace()函数轻松地进行修正。例如,s.replace('.','',1).replace('e-','',1).replace('e','',1).isdigit()可以处理指数。然后,为了处理负数,只需从第一个字符开始去除破折号。例如,s.lstrip('-').replace('.','',1).replace('e-','',1).replace('e','',1).isdigit()是的,我已经彻底测试了这个一行代码,并确认它的行为符合预期。 - Cecil Curry
显示剩余10条评论

83

有一个例外情况你可能需要考虑:字符串'NaN'

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

>>> float('NaN')
nan
否则,我实际上应该感谢你给我提供了这个我现在广泛使用的代码片段。 :)
G.

4
如果传入的文本不是数字的表示形式,实际上 NaN 可能是一个很好的返回值(而不是 False)。检查它有点麻烦(Python 的 float 类型确实需要一个相应的方法),但你可以在计算中使用它而不会产生错误,只需要检查结果即可。 - kindall
11
另一个例外是字符串'inf'infNaN也可以加上+-前缀仍然被接受。 - agf
4
如果你希望对NaN和Inf返回False,则需要更改代码行为x = float(s); return (x == x) and (x - 1 != x)。这将对除Inf和NaN以外的所有浮点数返回True。 - RyanN
6
对于小于无穷大的大浮点数,“x-1 == x”是正确的。从Python 3.2开始,您可以使用“math.isfinite”来测试既不是NaN也不是无限的数字,或在此之前先检查“math.isnan”和“math.isinf”两者都满足。 - Steve Jessop

67

这样怎么样:

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

仅当数字字符串中只包含一个或零个 '.' 时,它才会返回true。

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

将返回false

编辑:刚刚看到另一个注释...对于其他情况,可以添加.replace(badstuff,'',maxnum_badstuff)。如果您传递的是盐而不是任意调味品(参考:xkcd#974),这将很好:P


8
然而,这并不包括负数的情况。 - Michael Barton
6
指数形式的数字,例如 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')应该更好地确定一个数字(但不是所有的,我并不是在声称)。我不建议使用这个,最好还是使用提问者原来的代码。 - Baldrickk
如果您不喜欢这种解决方案,请在点踩之前阅读此链接。 - aloisdg
这是我在这个网站上见过的最聪明的解决方案!做得好啊! - Karam Qusai

49

在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

15
我不同意。在正常使用中,这种情况非常不可能出现,你最好建立一个is_complex_number()调用来处理它们,而不是为了0.0001%的误操作负担额外的操作。 - Jiminion
3
可以完全去掉float()部分,只需检查complex()调用是否成功即可。float()解析的所有内容都可以被complex()解析。 - Alfe
该函数将Pandas中的NaN和Inf值作为数值返回。 - fixxxer
1
complex('(01989)') 将返回 (1989+0j)。但是 float('(01989)') 会失败。因此我认为使用 complex 不是一个好主意。 - plhn
哎呀。complex() 接受 () 作为分隔符的语法很奇怪 - 大概是为了考虑到复数在虚数平面上的复合向量加法,但仍然很奇怪。正如 @plhn 所建议的那样,在这里使用 complex() 会导致误报。不要在生产代码中这样做。老实说,对于大多数用例,s.lstrip('-').replace('.','',1).replace('e-','',1).replace('e','',1).isdigit() 仍然是最优解。 - Cecil Curry

45

"Which, not only is ugly and slow, seems clunky."

这种写法不仅难看而且很慢,看起来笨拙。

可能需要一些时间来适应,但这是Pythonic的写法。正如已经指出的那样,其他方法更糟糕。但是用这种方式做事情还有一个优点:多态性。

鸭子类型背后的核心思想是“如果它走起路来和叫起来像一个鸭子,那么它就是一个鸭子”。假设你决定子类化字符串,以便可以更改确定某个内容是否可以转换为float的方式,或者你决定测试一些完全不同的对象怎么办?在不更改上面的代码的情况下,你可以做到这些。

其他语言通过使用接口来解决这些问题。我会将哪种解决方案更好的分析留给另一个主题。然而,重要的是,Python明显处于鸭子类型方程式的一边,如果你计划在Python中进行大量编程,你可能必须适应这种语法(但这并不意味着你必须喜欢它)。

还有一件事情您可能需要考虑:与许多其他语言相比,Python在抛出和捕获异常方面非常快(例如比.Net快30倍)。事实上,这种语言甚至会抛出异常来传达非异常的正常程序条件(每次使用for循环时都是如此)。因此,在注意到明显问题之前,我不会对该代码的性能方面过于担忧。


1
Python中另一个常见的使用异常处理基本函数的地方是在hasattr()中,它只是一个包装在try/except中的getattr()调用。然而,异常处理比正常流程控制慢,因此将其用于大多数情况下为真的事情可能会导致性能损失。 - kindall
看起来如果你想要一行代码,那就很难了。 - Basic
Pythonic的另一个概念是“宁愿请求原谅,也不要事先获得许可”,这与使用廉价异常的影响有关。 - heltonbiker

34
这篇答案提供了一步一步的指南,包括函数和示例,以查找字符串是否为:
  • 正整数
  • 正/负-整数/浮点数
  • 如何在检查数字时丢弃"NaN"(不是数字)字符串?

检查字符串是否为整数

您可以使用str.isdigit()来检查给定的字符串是否为整数。

样本结果:

# For digit
>>> '1'.isdigit()
True
>>> '1'.isalpha()
False

检查字符串是正数/负数 - 整数/浮点数

str.isdigit() 返回False,如果字符串是一个负数或浮点数。例如:

# returns `False` for float
>>> '123.3'.isdigit()
False
# returns `False` for negative number
>>> '-123'.isdigit()
False

如果你想要同时检查负整数和float,那么可以编写一个自定义函数进行检查:

如下:

def is_number(n):
    try:
        float(n)   # Type-casting the string to `float`.
                   # If string is not a valid `float`, 
                   # it'll raise `ValueError` exception
    except ValueError:
        return False
    return True

样例运行:

>>> is_number('123')    # positive integer number
True

>>> is_number('123.4')  # positive float number
True
 
>>> is_number('-123')   # negative integer number
True

>>> is_number('-123.4') # negative `float` number
True

>>> is_number('abc')    # `False` for "some random" string
False

在检查数字时忽略 "NaN" (不是数字) 字符串

上述函数将返回 True,因为对于Python来说,“NAN”(不是数字)字符串是有效的浮点数,表示它不是一个数字。例如:

>>> is_number('NaN')
True

为了检查数字是否为 "NaN",您可以使用 math.isnan(),如下所示:
>>> import math
>>> nan_num = float('nan')

>>> math.isnan(nan_num)
True

如果你不想导入额外的库来检查这个问题,那么你可以通过使用==将其与自身进行比较来简单地检查它。当将nan浮点数与自身进行比较时,Python返回False。例如:

# `nan_num` variable is taken from above example
>>> nan_num == nan_num
False

因此,上述的is_number函数可以更新为:当输入为"NaN"时,返回False
def is_number(n):
    is_number = True
    try:
        num = float(n)
        # check for "nan" floats
        is_number = num == num   # or use `math.isnan(num)`
    except ValueError:
        is_number = False
    return is_number

示例运行:

>>> is_number('Nan')   # not a number "Nan" string
False

>>> is_number('nan')   # not a number string "nan" with all lower cased
False

>>> is_number('123')   # positive integer
True

>>> is_number('-123')  # negative integer
True

>>> is_number('-1.12') # negative `float`
True

>>> is_number('abc')   # "some random" string
False

PS:每个检查操作都会带来额外的开销,具体取决于数字类型。选择适合您要求的is_number函数版本。


1
这应该放在顶部,因为它充分解决了关于“一个数字”的歧义,并展示了多种解释的相应解决方案。 - Karl Knechtel

34

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

3
不处理像 1.234e56 等类似的内容。另外,我想知道你是如何发现 99999999999999999999e99999999999999999999 不是一个数字的。尝试解析它会很快发现。 - Alfe
这个程序在一个包含5000万个字符串的列表上比已接受的解决方案快了约30%,在一个包含5千个字符串的列表上快了150%。 - Zev Averbach
请注意,>>> '--1234'.lstrip('-').replace('.','',1).isdigit() 返回 true;这可能不是预期的结果。 - brewmanz

18

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

我承认错误 :-} 它看起来似乎没有这样做。也许使用像prep_code_basisprep_code_re_method这样的名称可以防止我的错误。 - Alfe
你介意解释一下你的模块是怎么工作的吗,至少针对 isfloat 函数? - Solomon Ucko
@SolomonUcko 这里是字符串检查部分的源代码链接:https://github.com/SethMMorton/fastnumbers/blob/v1.0.0/src/string_contains_float.c。基本上,它按顺序遍历字符串中的每个字符,并验证它是否符合有效浮点数的模式。如果输入已经是数字,则只需使用快速的[PyFloat_Check](https://docs.python.org/3/c-api/float.html)。 - SethMMorton
3
在这个讨论串中,和其他最佳替代方案相比较,我确认这个解决方案是迄今为止最快的。第二快的方法是str(s).strip('-').replace('.','',1).isdigit(),速度大约慢了10倍! - Alexander McFarlane
请注意,timeit.timeit会运行该语句100万次。我曾经很困惑为什么这些数字看起来如此缓慢。 - mic
这个解决方案非常好,应该更靠近顶部,说实话。它需要一个新的库,但很容易使用,并且表现出直观的方式。正是我所寻找的。谢谢@SethMMorton - rfadams

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接