将字符串转换为浮点数,避免静默的NaN/Inf转换

1 投票
4 回答
8831 浏览
提问于 2025-04-15 23:36

我想在Python 2.6及以后的版本中把字符串转换成浮点数,但我希望在转换时不要默默地把像'NaN''Inf'这样的内容变成浮点对象。我希望这些无效的文本能被忽略,就像任何不符合浮点数表示的文本一样。

在2.6之前,float("NaN")在Windows上会抛出一个ValueError错误。但现在它会返回一个浮点数,而用math.isnan()检查这个浮点数会返回True,这对我的应用来说并没有什么用。(正如有人指出的,这种行为一直依赖于平台,但对我来说,无论在哪里发生,这都是一种不希望出现的行为。)

目前我有的代码是:

import math
def get_floats(source):
    for text in source.split():
        try:
            val = float(text)
            if math.isnan(val) or math.isinf(val):
                raise ValueError
            yield val
        except ValueError:
            pass

这是一个生成器,我可以给它提供包含用空格分开的真实数字序列的字符串。我希望它只返回那些完全是浮点数表示的字段,比如"1.23"或"-34e6",而不是像"NaN"或"-Inf"这样的内容。完全不是浮点数的东西,比如"hello",也应该被忽略。

测试案例:

assert list(get_floats('1.23 foo -34e6 NaN -Inf')) == [1.23, -34000000.0]

请建议一些你认为更优雅的替代方案,即使它们涉及到“先看再跳”(这在Python中通常被认为是一种较低级的做法)。

编辑以澄清像"hello"这样的非浮点文本也应该被安静地忽略。目的是只提取出那些真实的数字,忽略其他所有内容。

4 个回答

0

我给Paul Hankin的回答点了赞,因为它看起来更容易理解。不过,如果我不想把代码拆得太细,这里有一个我最初代码的变种,显得不那么笨重。

def get_only_numbers(source):
    '''yield all space-separated real numbers in source string'''
    for text in source.split():
        try:
            val = float(text)
        except ValueError:
            pass  # ignore non-numbers
        else:
            # "NaN", "Inf" get converted: explicit test to ignore them
            if not math.isnan(val) and not math.isinf(val):
                yield val

其实这和我最开始的代码差不多。

1

这是一个非常小的建议,不过使用 continue 这个命令比抛出一个错误要快一点:

def get_floats(source):
    for text in source.split():
        try:
            val = float(text)
            if math.isnan(val) or math.isinf(val): continue
            yield val
        except ValueError:
            pass

使用 raise ValueError 的情况:

% python -mtimeit -s'import test' "list(test.get_floats('1.23 -34e6 NaN -Inf Hello'))"
10000 loops, best of 3: 22.3 usec per loop

使用 continue 的情况:

% python -mtimeit -s'import test' "list(test.get_floats_continue('1.23 -34e6 NaN -Inf Hello'))"
100000 loops, best of 3: 17.2 usec per loop
2

我会这样写。我觉得这样既简洁又容易读懂。

def is_finite(x):
    return not math.isnan(x) and not math.isinf(x)

def get_floats(source):
    for x in source.split():
        try:
            yield float(x)
        except ValueError:
            pass

def get_finite_floats(source):
    return (x for x in get_floats(source) if is_finite(x))

撰写回答