【问题标题】:Python equivalent to C strtodPython 等价于 C strtod
【发布时间】:2011-11-25 18:23:13
【问题描述】:

我正在将 C++ 程序的一部分转换为 Python,但在替换 C 函数 strtod 时遇到了一些麻烦。 我正在处理的字符串由简单的数学方程式组成,例如“KM/1000.0”。问题是常量和数字都是混合的,因此我无法使用 float()。

如何编写 Python 函数来模拟 strtod,它返回转换后的数字和下一个字符的位置?

【问题讨论】:

标签: python strtod


【解决方案1】:

我不知道有任何现有的功能可以做到这一点。

但是,使用正则表达式很容易编写:

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)

更好的整体方法可能是构建一个lexical scanner,它首先对表达式进行标记,然后使用一系列标记而不是直接使用字符串(或者确实全力以赴并构建一个 yacc 样式的解析器)。

【讨论】:

    【解决方案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
    

    (如果您使用 Python 64 位,编译器也必须是 64 位)

    并使用 python 中的ctypes 调用它(linux:在 lib 目标和下面的代码中将 .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的有用问答

    【讨论】:

    • 似乎没有必要创建包装器;您应该可以直接使用strtod 来执行此操作。
    • 那会更好。我必须先测试一下:)
    • 您应该能够从特定于平台的现有共享库文件中加载strtodctypes.cdll.msvcrt 应该可以在 Windows 上运行。我相信它在 Linux 上通常是 cdtypes.CDLL('libc.so.6'),但我不知道它有多普遍。也可以编译您自己的文件以访问strtod,尽管我不确定其中的细节是什么样的。 (#include &lt;stdlib.h&gt; 本身似乎可行。)
    • 我已经尝试在 C 文件中单独包含单个 stdlib.h ,但似乎 strtod 符号未链接,因此它不起作用(python 找不到它)。现在坚持空包装。它正在工作,并且除了 .dll/.so 部分之外,它在源代码级别是可移植的。如答案所述,我不是 ctypes 专家。刚刚让它工作(并且对 python 代码的简单性印象深刻)。
    【解决方案3】:

    自己解析数字。

    递归下降解析器非常容易处理这种输入。 先写一个语法:

    float ::= ipart ('.' fpart)* ('e' exp)*
    ipart ::= digit+
    fpart ::= digit+
    exp   ::= ('+'|'-') digit+
    digit = ['0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9']
    

    现在将此语法转换为函数应该很简单......

    【讨论】:

    • float的定义中ipart之前应该有一个('+'|'-')
    • @madphysicist 这取决于上下文。解析单个独立数字时,确实需要解析前导符号。解析数字表达式时,请避免包含符号,因为它会允许像“42-+37.2”这样的奇怪表达式(我似乎记得我从一种众所周知的语言的语法中复制了这个语法)
    • 42-+37.2 对我来说似乎是一个合理的表达方式。
    • 虽然对任何数学倾向的人来说都是合理的,但这样的语法意味着你也可以写42--37.2,这会混淆 C 或 C++ 解析器(但奇怪的是 C++ 接受 42-+37.2)。因此,许多(大多数)编程语言将前导符号视为一元运算符,即与后面的数字明显分开的实体。并且某些语言不允许在表达式开头以外的任何地方使用一元运算符。反正对于独立数的简单解析,上面的语法确实是少了那些一元运算符。
    【解决方案4】:

    我会为此使用正则表达式:

    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)
    

    【讨论】:

    • 我能想到一堆不会被拾取的有效浮点数。
    • 正则表达式采用整数部分。其他一切都是可选的。如果有小数点,则需要小数部分。所以.11. 不会被接收。当然,必要时修改正则表达式是微不足道的。
    猜你喜欢
    • 2012-05-11
    • 2011-03-02
    • 1970-01-01
    • 1970-01-01
    • 2018-09-23
    • 1970-01-01
    • 2014-09-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多