是否有一种pythonic方法来验证字符串是否表示浮点数(任何可由float()识别的输入,例如-1.6e3),而不转换它(理想情况下,不使用抛出和捕获异常)?
前面的问题已经提交了,关于如何检查字符串是否表示整数或浮点。答案建议在用户定义的函数中使用try...except子句和int()和float()内置项。
然而,这些问题并没有正确解决速度问题。虽然使用try...except的习惯用法将转换过程与验证过程联系起来(在某种程度上是正确的),但是为了验证目的而遍历大量文本的应用程序(任何模式验证器、解析器)将承受执行实际转换的开销。除了由于数字的实际转换而导致的减速之外,还有由于抛出和捕获异常而导致的减速。此Github Gist演示了与仅用户定义的验证相比,内置转换代码的成本是原来的两倍(比较True种情况),仅try..except版本的异常处理时间(False时间减去True时间)就高达7次验证。这就回答了我关于整数的问题。
有效的答案将是:以比try..except方法更有效的方式解决问题的函数,对将来允许这样做的内置功能的文档的引用,对现在允许这样做的python包的引用(并且比try..except方法更有效),或指向documentatio的解释。为什么这样的解决方案不是Python式的,否则将永远无法实现。具体来说,为了防止混乱,请避免回答"否",而不指向官方文件或邮件列表辩论,并避免重复使用try..except方法。
- 你看到这个了吗?stackoverflow.com/questions/736043/…
- 很抱歉,但我不得不问-您是否衡量过这项检查是您的应用程序的瓶颈?
- 我读过,尽管我没有读到最后。但是,partition()方法不适用于指数(尽管我可能会使其生效),并且可以接受的答案是try..except代码。
- @罗格尔斯基-不。可能不是,但这不是问题。
- 你可以用正则表达式…或者是Python用来确定静态数字是否为浮点的解析功能。不知道这是否比试一下更有效
- @但是这是个问题,你的问题是假设除了尝试方法会导致太多的开销。如果没有真正弄乱那假定的开销,整个问题就没有意义了。
- @E先生-见int()案。
正如@john在评论中提到的,这似乎是另一个问题的答案,尽管在这种情况下,这不是公认的答案。正则表达式和fastnumbers模块是这个问题的两个解决方案。
然而,有人适时地指出(正如@en_Knight所做的那样),性能在很大程度上取决于输入。如果期望大部分有效的输入,那么EAFP方法更快,而且可以说更优雅。如果您不知道应该输入什么,那么lbyl可能更合适。从本质上来说,验证应该期望大部分有效的输入,因此它更适合于try..except。
事实上,对于我的用例(作为问题的编写者,它具有相关性),在表格式数据文件中标识数据类型,try..except方法更为合适:列要么全部是浮点型的,或者,如果它具有非浮点型的值,则从它上面的行被认为是文本型的,因此实际测试浮点型的大多数输入在不管是哪种情况。我想其他的答案都是有道理的。
回到答案,fastnumber和正则表达式对于一般情况仍然是有吸引力的解决方案。具体来说,fastnumbers包似乎对所有值都很有效,除了特殊值,如github gist中所示的Infinity、Inf和NaN。上述答案中的简单正则表达式也是如此(稍微修改-删除尾随的\b,因为它会导致一些输入失败):
1
| ^[-+]?(?:\b[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][-+]?[0-9]+\b)?$ |
在GIST中使用了一个更大的版本,它可以识别特殊的值,并且具有相同的性能:
1
| ^[-+]?(?:[Nn][Aa][Nn]|[Ii][Nn][Ff](?:[Ii][Nn][Ii][Tt][Yy])?|(?:\b[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][-+]?[0-9]+\b)?)$ |
号
对于有效输入,正则表达式的实现速度要慢2.8倍,而对于无效输入,则要快2.2倍。无效输入比使用try..except的有效输入慢5倍,或使用正则表达式快1.3倍。鉴于这些结果,这意味着当40%或更多的预期输入无效时,使用正则表达式是有利的。
对于有效输入,fastNumber的速度仅为~1.2倍,而对于无效输入,fastNumber的速度则为~6.3倍。
结果如下图所示。我用10^6次重复运行,有170个有效输入和350个无效输入(相应地加权,因此平均时间是每个输入)。因为框太窄,所以不显示颜色,但每列左侧的框描述了有效输入的时间安排,而无效输入则显示在右侧。
。
注意,答案被多次编辑,以反映对问题、此答案和其他答案的评论。为了清晰起见,编辑已合并。有些评论引用了以前的版本。
- 如果正则表达式是一个解决方案,这是否意味着您要查找一个受限制的浮点子集?我发现很难相信有一个正则表达式,它的强大程度足以捕获一般的浮点转换(true和"1.4"但不是"true")。
- @恩奈特:在这种情况下,正则表达式足够强大。(再说一次,我不知道你在用True做什么,这是不相关的。)请看下面的python的decimal模块认为可以接受的,它在语法上与float接受的非常接近。
- @markdickinson这个问题问"任何可以被float()识别的输入"。由于float(true)返回1.0,您的实现应该做同样的事情来满足这个需求,对吗?float方法还转换hex,如果我正确使用它,上面的regex似乎不能正确地进行转换。
- @你所说的X倍更快是什么意思?这是在它通过或失败的情况下?Try/Catch在不引发异常的情况下非常有效;您使用了什么分布的案例?你在检查每种情况的最坏情况吗(Regex的最坏情况似乎很难找到)?
- @en-Knight-您可以查看输入列表的要点(链接自答案)。这是有效和无效输入的混合体。""x乘以更快"意味着如果try..except方法需要4.5秒,regex方法需要3秒(对于相同的输入),fastnumbers方法需要约1秒。关于十六进制输入,我不知道,在float()中有特殊处理。你指的是float.fromhex()?
- @尤瓦尔1号。好吧,听起来很公平;注意,这可能是一个非常有偏见的单元测试。当我测试["1"]*10000作为输入时,try/catch比regex方法快1.5倍。这取决于您的输入是什么;如果您期望有很多未命中,那么我相信您的regex更快——如果您只期望有一些未命中,那么它看起来像是一个误导性测试。2。float(0x0)返回0.0。如果您试图使try/catch的行为完全相同,那么regex不会处理这个问题。
- @恩骑士埃多克斯1(6)->埃多克斯1(7)。不带引号的0x0是一个数字文本,它与作为输入的这个问题无关。
如果身为Python是一种正当理由,那么你应该坚持使用Python的禅。具体来说:
Explicit is better than implicit.
Simple is better than complex.
Readability counts.
There should be one-- and preferably only one --obvious way to do it.
If the implementation is hard to explain, it's a bad idea.
号
所有这些都支持尝试,除了方法。转换是明确的、简单的、可读的、明显的和易于解释的。
另外,知道某个东西是否是浮点数的唯一方法是测试它是否是浮点数。这听起来可能是多余的,但不是
现在,如果主要问题是在尝试测试太多假定的浮点数时速度问题,那么可以使用一些带有cython的C扩展来同时测试所有浮点数。但我并不认为它会在速度方面给你带来太多的改进,除非要尝试的字符串数量真的很大
编辑:
python开发人员倾向于使用EAFP方法(请求原谅比请求允许更容易),使得尝试except方法更像Python(我找不到PEP)
这里(Python中异常处理程序的成本)比较了try-except方法和if-then方法。事实证明,在Python中,异常处理并不像在其他语言中那样昂贵,而且只有在必须处理异常的情况下才更昂贵。在一般的用例中,您不会试图验证一个具有很高概率的字符串,而实际上它不是一个浮点数(除非在您的特定场景中有这种情况)。
正如我在评论中所说。如果没有特定的用例、要测试的数据和时间度量,整个问题就没有那么多意义。只谈最一般的用例,试一下"例外"是一种方法,如果你有一些实际的需求不能足够快地满足它,那么你应该把它添加到问题中。
- 我同意,进行转换和验证是有好处的,我提到过。但是,您会混淆测试它是否是一个浮点数,并将其转换为一个浮点数,而这些浮点数是不同的,而且在某些情况下,如我所指出的,用户可能会受益于不进行转换的验证。你的答案不能解释为什么验证不会是Python。事实上,在验证的情况下,除了尝试之外的方法比isfloat()内置的方法更复杂。这样的实现很容易解释,更易读,更明显,更明确。
- 尽管我不同意特定用例是相关的,因为这是一个非常普遍的问题,但我提到过根据模式验证大型文本文件。具体地说,我正在创建一个脚本,从表格数据文件中推断列数据类型,以便将它们正确有效地导入数据库。
- @尤瓦尔刚刚更新了我的答案。虽然转换和验证是不一样的。作为一个内置的isFloat可能更简单,但它的实现既不容易也不可读。如果您想要这个实现,它可能在float()函数内部。
- @在python github repo中,在cpython/objects/floatobject.c下,您可以了解如何实现到float的转换。你可以从那里学到怎么做,我没有找到任何模块可以按照你想要的方式来做。所以你有这两个选择,使用或不使用赛通来实现它(出于性能原因),或者坚持尝试,除了
如果你确定你想要的话,快速解决
看看这个参考实现——在python中转换为float是在C代码中进行的,而且执行效率非常高。如果您真的担心开销,可以将该代码逐字复制到自定义C扩展中,但不必提升错误标志,而是返回一个指示成功的布尔值。
特别是,看一下强制十六进制变为浮点的复杂逻辑。这是在C级别完成的,有很多错误案例;这里似乎不太可能有捷径(请注意,40行注释支持一个特定的保护案例),或者在保存这些案例的同时,任何手动执行都将更快。
但是…有必要吗?
作为一个假设,这个问题很有趣,但是在一般情况下,我们应该尝试分析它们的代码,以确保try-catch方法增加了开销。Try/Catch通常是惯用的,而且根据您的使用情况可以更快。例如,对于Python中的循环,请使用try/catch by design。
还有我为什么不喜欢它们
为了澄清,问题是
any input that would be recognizable by float()
号
备选方案1——Regex怎么样?
我很难相信你会得到一个正则表达式来解决这个问题。虽然regex可以很好地捕获浮点文本,但有很多角情况。看看这个答案上的所有情况-你的regex处理NaN吗?指数?bools(但不是bool字符串)?
备选2:手动打开python检查:
总结需要捕获的困难情况(Python本身就是这样做的)
- NAN的不区分大小写捕获
- 十六进制匹配
- 语言规范中列举的所有案例
- 符号,包括指数中的符号
- 布尔值
我还将向您指出下面的情况:语言规范中的浮点;虚数。浮动方法通过识别它们是什么来优雅地处理它们,但在转换时会引发类型错误。你的自定义方法会模仿这种行为吗?
- 大多数答案都应该作为评论发布。参考实现参考是有用的,谢谢。
- 嗯,哪部分?不使用regex似乎与我的答案直接相关,因为我以前写过解析器,"使用regex"有时是一个有用的答案,但在这里并不适用。关于"简介;不重要"是否是答案的有效部分,有很多讨论,但我觉得如果我不包括它,我的答案对未来的观众来说是不完整的。对你来说,你可能知道这个瓶颈是适用的-我不太确定下一个阅读这个答案的人
- 我已经在问题的正文中证明了验证与转换对于int()的相关性。在缺乏替代实现(编写问题时)的情况下,无法将一个实现与另一个实现进行比较。在我的回答中(有些人投了低票,但没有解释原因,这让我感到惊讶,因为——虽然可能不完整,但远没有什么用处),我增加了一些基准时间。
- "十六进制匹配"在这里是什么意思?python的float构造函数不接受任何十六进制表示形式的字符串。与布尔值相同:float("True")无效。(float(True)是,但当OP特别询问字符串时,这似乎不相关。)
- 我谦虚地建议,为了改进您的答案:我会将"please profile"改为类似"Do your use case call for such optimizations"。具体地说,我在我的问题中证明了无效输入是相关的,并导致int版本的运行速度慢得多。除此之外,我将删除您答案的所有其他部分,并指向引用源代码,因为其他部分完全是推测性的,因为从"我认为没有更好的解决方案",并且与我提供的答案(建议正则表达式)相对应,而不是我的问题。
- 公平地说,我会做一些编辑(可能会留下比你想要的更多,但我会看到我能放弃多少:)。@markdickinson这个问题以"float()可以识别的任何输入"开头,这就是我要回答的问题;float方法接受十六进制数,但不接受十六进制数。我的回答是:我想要和try一样的行为:float(thing);except:pass,如果不调用该方法很难做到。
- @尤瓦尔希望这样更好——我把"答案"部分放在最上面,并把反对替代方案的争论作为一个事后考虑。我认为,说为什么其他方法不可取是答案的一个有效部分,特别是当它们在子类中看起来是好主意时,但欢迎您提出异议,毕竟这是您的问题:)
- 谢谢@en_Knight。正如其标题所暗示的,这个问题是关于字符串的。注意到float()接受其他输入类型是有用的——尽管可以很容易地将其与instanceof()结合起来。推测是好的,但不是一个答案——最好把你对正则表达式的怀疑留给实际的建议(如我写的建议)。第二种选择是不连贯的:链接指向其他东西(?)不清楚"十六进制匹配"是什么,以及虚数串接收到什么特殊处理。其余部分重复备选方案1,虽然是一个很好的总结,但最好留下来作为问题的评论。
为了证明这一点,一个字符串不需要遵守多少条件,就可以使它成为float型。但是,在Python中检查所有这些条件将非常缓慢。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| ALLOWED ="0123456789+-eE."
def is_float(string):
minuses = string.count("-")
if minuses == 1 and string[0] !="-":
return False
if minuses > 1:
return False
pluses = string.count("+")
if pluses == 1 and string[0] !="+":
return False
if pluses > 1:
return False
points = string.count(".")
if points > 1:
return False
small_es = string.count("e")
large_es = string.count("E")
es = small_es + large_es
if es > 1:
return False
if (es == 1) and (points == 1):
if small_es == 1:
if string.index(".") > string.index("e"):
return False
else:
if string.index(".") > string.index("E"):
return False
return all(char in ALLOWED for char in string) |
我没有测试过这个,但我敢打赌这比try: float(string); return True; except Exception: return False慢得多。
- 对于Nan,这是否成功返回?看起来不像…
- 不,不是这样。那只会让手术更糟:p