在Python中检查字符串是否可以转换为浮点数

295

我有一些Python代码,它会遍历一个字符串列表并将它们转换为整数或浮点数(如果可能的话)。对于整数,这很容易实现。

if element.isdigit():
  newelement = int(element)

浮点数更难处理。目前我正在使用 partition('.') 来分割字符串,并检查一侧或两侧是否为数字。

partition = element.partition('.')
if (partition[0].isdigit() and partition[1] == '.' and partition[2].isdigit()) 
    or (partition[0] == '' and partition[1] == '.' and partition[2].isdigit()) 
    or (partition[0].isdigit() and partition[1] == '.' and partition[2] == ''):
  newelement = float(element)

这个方法可行,但是显然if语句有点复杂。我考虑的另一个解决方案是用try/catch代码块来尝试转换并查看是否成功,就像这个问题中描述的那样。

大家有其他的想法吗?对分隔符和try/catch方法的优劣有什么看法?


准确地说,在Python中并不存在类型转换这样的概念。因此,标签“type-conversion”是误导性的,而在某些语言中,它是一个明确定义的术语。 - user5538922
@bombs 没有类型转换?那这是什么:print(type("1"));print(type(int("1"))),输出:<class 'str'> <class 'int'>?这不是从 strint 的类型转换吗? - ggorlen
@ggorlen 一点也不。你只是从其他对象创建新对象。沿途在堆中分配新的内存空间。没有任何转换。 - user5538922
1
我明白你的意思,但那似乎有点学究了。如果有人说“将字符串转换为浮点数”,意图似乎是非常普遍清晰的。 - ggorlen
你为什么使用.isdigit()而不是.isnumeric()来判断正整数? - Charlie Parker
24个回答

428

我会直接使用..

try:
    float(element)
except ValueError:
    print "Not a float"

这很简单,它能够正常工作。请注意,如果元素为例如1<<1024,它仍然会抛出OverflowError。

另一个选择是使用正则表达式:

import re
if re.match(r'^-?\d+(?:\.\d+)$', element) is None:
    print "Not float"

3
大多数应用到这里的字符串将被证明是整数或浮点数。 - Chris Upchurch
10
你的正则表达式不够优化。使用"^\d+.\d+$"可以与上述表达式同样快地失败匹配,但可以更快地成功匹配。另外,更正确的方法是:"^[+-]?\d(>?.\d+)?$" 但这仍无法匹配类似于:+1.0e-10这样的数字。 - John Gietzen
156
除了你忘记给函数命名为“will_it_float”之外,没有其他问题。 - unmounted
5
第二个选项无法匹配NaN和指数表达式,例如2e3。 - Patrick B.
4
我认为正则表达式无法解析负数。 - Carlos
显示剩余6条评论

308

Python3检查浮点数的方法:

def is_float(element: any) -> bool:
    #If you expect None to be passed:
    if element is None: 
        return False
    try:
        float(element)
        return True
    except ValueError:
        return False

以上的Python2版本:如何将字符串解析为浮点数或整数?

始终进行单元测试。什么是浮点数,什么不是浮点数,可能会让你感到惊讶:

Command to parse                        Is it a float?  Comment
--------------------------------------  --------------- ------------
print(isfloat(""))                      False
print(isfloat("1234567"))               True 
print(isfloat("1_2_3.4"))               True        123.4, underscores ignored
print(isfloat("NaN"))                   True        nan is also float
print(isfloat("123.456"))               True
print(isfloat("123.E4"))                True
print(isfloat(".1"))                    True
print(isfloat("6.523537535629999e-07")) True
print(isfloat("6e777777"))              True        This is same as Inf
print(isfloat("-iNF"))                  True
print(isfloat("1.797693e+308"))         True
print(isfloat("infinity"))              True
print(isfloat("1,234"))                 False
print(isfloat("NULL"))                  False       case insensitive
print(isfloat("NaNananana BATMAN"))     False
print(isfloat(",1"))                    False           
print(isfloat("123.EE4"))               False           
print(isfloat("infinity and BEYOND"))   False
print(isfloat("12.34.56"))              False       Two dots not allowed.
print(isfloat("#56"))                   False
print(isfloat("56%"))                   False
print(isfloat("0E0"))                   True
print(isfloat("x86E0"))                 False
print(isfloat("86-5"))                  False
print(isfloat("True"))                  False       Boolean is not a float.   
print(isfloat(True))                    True        Boolean is a float
print(isfloat("+1e1^5"))                False
print(isfloat("+1e1"))                  True
print(isfloat("+1e1.3"))                False
print(isfloat("+1.3P1"))                False
print(isfloat("-+1"))                   False
print(isfloat("(1)"))                   False       brackets not interpreted

像这样忽略异常是不好的,因为杀死canaries是不好的,因为float方法可能由于除用户输入之外的原因而失败。不要在生命关键软件上使用此类代码。此外,Python一直在更改其关于将unicode字符串提升为float的规定,因此预计此代码的行为会在主要版本更新时发生变化。


12
很棒的回答。只是再加上两个 float=True 的例子:isfloat(" 1.23 ")isfloat(" \n \t 1.23 \n\t\n")。在网络请求中很有用;无需先修剪空格。 - BareNakedCoder
7
可能会让人惊讶的另一个新增功能是:isfloat("1_2_3.4") -> True。该函数用于判断字符串是否能被解释为浮点数。 - dariober
你可能也想要捕获 isfloat(None)isfloat(False) 的类型错误。 - Levi
@dariober 为什么? - Ralph
3
应该是“任何”,而不是“Any”。除此之外看起来没问题! - Harshit Hiremath

63
'1.43'.replace('.','',1).isdigit()

当且仅当数字字符串中有一个或零个 "." 时,它才会返回 true

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

将返回false

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

将返回false


6
不是最优解但实际上相当聪明。无法处理加减运算和指数。 - Mad Physicist
6
当你了解负数的存在时,你可能会感到震惊。 - David Heffernan
1
我喜欢这种情况下最好的一行代码,只需检查正浮点数或数字。 - MJohnyJ
3
am_i_a_number.strip().lstrip('-').replace('.', '', 1).isdigit() - Hawkeye Parker
1
@HawkeyeParker 哈哈,不错的反弹。在评论区保持颜面加1。 - rv.kvetch
显示剩余2条评论

16

TL;DR:

  • 如果您的输入大部分是可以转换为浮点数的字符串,则try: except:方法是最佳的本机Python方法。
  • 如果您的输入主要是无法转换为浮点数的字符串,则正则表达式或划分方法更好。
  • 如果您不确定您的输入或需要更快的速度并且可以安装第三方C扩展程序,则fastnumbers非常有效。

还有一种方法可通过第三方模块fastnumbers使用(声明,我是作者);它提供了一个名为isfloat的函数。 我采用了Jacob Gabrielson在此答案中概述的单元测试示例,但添加了fastnumbers.isfloat方法。 我还应该注意的是,Jacob的示例没有充分说明正则表达式选项,因为由于点运算符,该示例中的大部分时间都花费在全局查找上... 我修改了该函数以使其与try: except:进行公平比较。


def is_float_try(str):
    try:
        float(str)
        return True
    except ValueError:
        return False

import re
_float_regexp = re.compile(r"^[-+]?(?:\b[0-9]+(?:\.[0-9]*)?|\.[0-9]+\b)(?:[eE][-+]?[0-9]+\b)?$").match
def is_float_re(str):
    return True if _float_regexp(str) else False

def is_float_partition(element):
    partition=element.partition('.')
    if (partition[0].isdigit() and partition[1]=='.' and partition[2].isdigit()) or (partition[0]=='' and partition[1]=='.' and partition[2].isdigit()) or (partition[0].isdigit() and partition[1]=='.' and partition[2]==''):
        return True
    else:
        return False

from fastnumbers import isfloat


if __name__ == '__main__':
    import unittest
    import timeit

    class ConvertTests(unittest.TestCase):

        def test_re_perf(self):
            print
            print 're sad:', timeit.Timer('ttest.is_float_re("12.2x")', "import ttest").timeit()
            print 're happy:', timeit.Timer('ttest.is_float_re("12.2")', "import ttest").timeit()

        def test_try_perf(self):
            print
            print 'try sad:', timeit.Timer('ttest.is_float_try("12.2x")', "import ttest").timeit()
            print 'try happy:', timeit.Timer('ttest.is_float_try("12.2")', "import ttest").timeit()

        def test_fn_perf(self):
            print
            print 'fn sad:', timeit.Timer('ttest.isfloat("12.2x")', "import ttest").timeit()
            print 'fn happy:', timeit.Timer('ttest.isfloat("12.2")', "import ttest").timeit()


        def test_part_perf(self):
            print
            print 'part sad:', timeit.Timer('ttest.is_float_partition("12.2x")', "import ttest").timeit()
            print 'part happy:', timeit.Timer('ttest.is_float_partition("12.2")', "import ttest").timeit()

    unittest.main()
在我的机器上,输出为:

fn sad: 0.220988988876
fn happy: 0.212214946747
.
part sad: 1.2219619751
part happy: 0.754667043686
.
re sad: 1.50515985489
re happy: 1.01107215881
.
try sad: 2.40243887901
try happy: 0.425730228424
.
----------------------------------------------------------------------
Ran 4 tests in 7.761s

OK

正如您所看到的,正则表达式实际上并不像最初看起来那么糟糕,如果您需要快速处理数据,fastnumbers 方法非常不错。


快速数字检查在大多数字符串无法转换为浮点数时表现得非常出色,真的可以加快速度,谢谢。 - ragardner
将变量命名为 str 会覆盖内置函数。 - ggorlen

7
如果你关心性能(我并不建议你这样做),使用基于try的方法是明显的赢家(与基于分区的方法或正则表达式方法相比),只要你不期望有很多无效字符串,否则它可能会更慢(可能是由于异常处理的成本)。
再次强调,我并不建议你关心性能,只是提供数据以防你每秒要执行10亿次或其他什么情况。此外,基于分区的代码至少不能处理一个有效字符串。
$ ./floatstr.py
F..
partition sad: 3.1102449894
partition happy: 2.09208488464
..
re sad: 7.76906108856
re happy: 7.09421992302
..
try sad: 12.1525540352
try happy: 1.44165301323
.
======================================================================
FAIL: test_partition (__main__.ConvertTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "./floatstr.py", line 48, in test_partition
    self.failUnless(is_float_partition("20e2"))
AssertionError

----------------------------------------------------------------------
Ran 8 tests in 33.670s
FAILED (failures=1)

这是代码(Python 2.6,正则表达式取自John Gietzen的answer):

def is_float_try(str):
    try:
        float(str)
        return True
    except ValueError:
        return False

import re
_float_regexp = re.compile(r"^[-+]?(?:\b[0-9]+(?:\.[0-9]*)?|\.[0-9]+\b)(?:[eE][-+]?[0-9]+\b)?$")
def is_float_re(str):
    return re.match(_float_regexp, str)


def is_float_partition(element):
    partition=element.partition('.')
    if (partition[0].isdigit() and partition[1]=='.' and partition[2].isdigit()) or (partition[0]=='' and partition[1]=='.' and pa\
rtition[2].isdigit()) or (partition[0].isdigit() and partition[1]=='.' and partition[2]==''):
        return True

if __name__ == '__main__':
    import unittest
    import timeit

    class ConvertTests(unittest.TestCase):
        def test_re(self):
            self.failUnless(is_float_re("20e2"))

        def test_try(self):
            self.failUnless(is_float_try("20e2"))

        def test_re_perf(self):
            print
            print 're sad:', timeit.Timer('floatstr.is_float_re("12.2x")', "import floatstr").timeit()
            print 're happy:', timeit.Timer('floatstr.is_float_re("12.2")', "import floatstr").timeit()

        def test_try_perf(self):
            print
            print 'try sad:', timeit.Timer('floatstr.is_float_try("12.2x")', "import floatstr").timeit()
            print 'try happy:', timeit.Timer('floatstr.is_float_try("12.2")', "import floatstr").timeit()

        def test_partition_perf(self):
            print
            print 'partition sad:', timeit.Timer('floatstr.is_float_partition("12.2x")', "import floatstr").timeit()
            print 'partition happy:', timeit.Timer('floatstr.is_float_partition("12.2")', "import floatstr").timeit()

        def test_partition(self):
            self.failUnless(is_float_partition("20e2"))

        def test_partition2(self):
            self.failUnless(is_float_partition(".2"))

        def test_partition3(self):
            self.failIf(is_float_partition("1234x.2"))

    unittest.main()

6

is_digit(str)的简化版本,适用于大多数情况(不考虑指数表示"NaN"值):

def is_digit(str):
    return str.lstrip('-').replace('.', '').isdigit()

2
非常好,优雅。 - étale-cohomology

6

这里提供另一种方法来实现它。

>>> all([i.isnumeric() for i in '1.2'.split('.',1)])
True
>>> all([i.isnumeric() for i in '2'.split('.',1)])
True
>>> all([i.isnumeric() for i in '2.f'.split('.',1)])
False

编辑:我确信它不能应对所有的浮点数情况,特别是当指数存在时。为了解决这个问题,看起来应该是这样的。只有在val为浮点数时才返回True,对于整数则返回False,但可能比正则表达式的性能差一些。

>>> def isfloat(val):
...     return all([ [any([i.isnumeric(), i in ['.','e']]) for i in val],  len(val.split('.')) == 2] )
...
>>> isfloat('1')
False
>>> isfloat('1.2')
True
>>> isfloat('1.2e3')
True
>>> isfloat('12e3')
False

isnumeric函数看起来不是一个好的选择,因为它会在各种Unicode字符上返回true,比如分数。文档说:“数字字符包括数字字符和所有具有Unicode数字值属性的字符,例如U+2155,VULGAR FRACTION ONE FIFTH”。 - gwideman

4
如果您不需要考虑数字的科学表示法或其他表达方式,而只是处理可能带有或不带有小数点的字符串: 功能
def is_float(s):
    result = False
    if s.count(".") == 1:
        if s.replace(".", "").isdigit():
            result = True
    return result

Lambda版本

is_float = lambda x: x.replace('.','',1).isdigit() and "." in x

示例

if is_float(some_string):
    some_string = float(some_string)
elif some_string.isdigit():
    some_string = int(some_string)
else:
    print "Does not convert to int or float."

这样就可以避免将本应是整数的数据误转为浮点数。


你也可以像这样放置运算符。def is_float(s): result = False if s.count(".") == 1 and s.replace(".", "").isdigit(): result = True return result - Gaurav Koradiya

3

这个正则表达式将检查科学计数法的浮点数:

^[-+]?(?:\b[0-9]+(?:\.[0-9]*)?|\.[0-9]+\b)(?:[eE][-+]?[0-9]+\b)?$

然而,我认为最好的办法是在try语句中使用解析器。

2

我已经使用了之前提到的函数,但很快我发现"Nan"、"Inf"及其变体被视为数字。因此,我向您提出改进后的函数版本,对这些类型的输入将返回false,并且不会失败于"1e3"变体:

def is_float(text):
    # check for nan/infinity etc.
    if text.isalpha():
        return False
    try:
        float(text)
        return True
    except ValueError:
        return False

1
我们能否直接从 if text.isalpha(): 检查开始呢? - Csaba Toth
顺便说一下,我需要相同的:我不想接受NaN、Inf和其他东西。 - Csaba Toth

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