Python中与C的strtod等效的实现
我正在把一个C++程序的部分代码转换成Python,但在替换C语言的一个函数 strtod 时遇到了一些麻烦。
我处理的字符串包含一些简单的数学表达式,比如“KM/1000.0”。问题是常量和数字混在一起,所以我不能直接使用float()函数。
我该如何写一个Python函数,来模拟 strtod
的功能,既能返回转换后的数字,又能返回下一个字符的位置呢?
4 个回答
0
我会用一种叫做正则表达式的工具来解决这个问题:
import re
mystring = "1.3 times 456.789 equals 593.8257 (or 5.93E2)"
def findfloats(s):
regex = re.compile(r"[+-]?\b\d+(?:\.\d+)?(?:e[+-]?\d+)?\b", re.I)
for match in regex.finditer(mystring):
yield (match.group(), match.start(), match.end())
这个工具可以在字符串中找到所有的小数,并且把它们和它们的位置一起返回。
>>> for item in findfloats(mystring):
... print(item)
...
('1.3', 0, 3)
('456.789', 10, 17)
('593.8257', 25, 33)
('5.93E2', 38, 44)
2
你可以创建一个简单的 C 语言的 strtod
包装器:
#include <stdlib.h>
double strtod_wrap(const char *nptr, char **endptr)
{
return strtod(nptr, endptr);
}
编译时使用:
gcc -fPIC -shared -o libstrtod.dll strtod.c
(如果你使用的是 64 位的 Python,编译器也必须是 64 位的)
然后可以通过 ctypes
在 Python 中调用它(在 Linux 上:把 .dll
改成 .so
,在下面的代码中也是这样,这个是在 Windows 上测试的):
import ctypes
_strtod = ctypes.CDLL('libstrtod.dll')
_strtod.strtod_wrap.argtypes = (ctypes.c_char_p, ctypes.POINTER(ctypes.c_char_p))
_strtod.strtod_wrap.restype = ctypes.c_double
def strtod(s):
p = ctypes.c_char_p(0)
s = ctypes.create_string_buffer(s.encode('utf-8'))
result = _strtod.strtod_wrap(s, ctypes.byref(p))
return result,ctypes.string_at(p)
print(strtod("12.5hello"))
输出结果是:
(12.5, b'hello')
(这并没有看起来那么难,因为我刚学会怎么做,才花了 10 分钟)
关于 ctypes
的一些有用问答
4
我不知道有没有现成的函数可以做到这一点。
不过,使用正则表达式自己写一个其实很简单:
import re
# returns (float,endpos)
def strtod(s, pos):
m = re.match(r'[+-]?\d*[.]?\d*(?:[eE][+-]?\d+)?', s[pos:])
if m.group(0) == '': raise ValueError('bad float: %s' % s[pos:])
return float(m.group(0)), pos + m.end()
print strtod('(a+2.0)/1e-1', 3)
print strtod('(a+2.0)/1e-1', 8)
一个更好的方法可能是先构建一个词法分析器,这样可以先把表达式分解成一个个小的部分(我们称之为“标记”),然后再处理这些标记,而不是直接处理整个字符串。或者更进一步,可以构建一个类似yacc的解析器。