pyparsing - 用千位分隔符解析数字

pyparsing - Parse numbers with thousand separators

所以我正在制作一个解析器,我发现了一个问题。事实上,为了解析数字,我有:

from pyparsing import Word, nums
n = Word(nums)

这适用于没有千位分隔符的数字。例如,n.parseString("1000", parseAll=True) returns (['1000'], {}) 因此有效。

但是,当我添加千位分隔符时它不起作用。事实上,n.parseString("1,000", parseAll=True) 提高了 pyparsing.ParseException: Expected end of text, found ',' (at char 1), (line:1, col:2).

如何解析带有千位分隔符的数字?我不只是想忽略逗号(例如,n.parseString("1,00", parseAll=True) 应该 return 一个错误,因为它不是数字)。

当您首先处理字符串时,您可以很好地对其使用正则表达式以确保它确实是一个数字(包括 thousand sep)。如果是,替换每个逗号并将其提供给解析器:

import re
from pyparsing import Word, nums
n = Word(nums)

def is_number(number):
    rx = re.compile(r'^-?\d+(?:,\d{3})*$')
    if rx.match(number):
        return number.replace(",", "")
    raise ValueError

try:
    number = is_number("10,000,000")
    print(n.parseString(number, parseAll=True))
except ValueError:
    print("Not a number")

有了这个,例如1,00 将导致 Not a number,请参阅 regex101.com.

上的表达式演示

我不太明白你所说的“数字有千位分隔符”是什么意思。

无论如何,使用 pyparsing 你应该定义你想要解析的模式。

在第一个示例中,pyparse 运行良好只是因为您将 n 定义为一个数字,所以:

n = Word(nums)
print(n.parseString("1000", parseAll=True))
['1000']

因此,如果要解析“1,000”或“1,00”,则应将 n 定义为:

n = Word(nums) + ',' + Word(nums)

print(n.parseString("1,000", parseAll=True))
['1', ',', '000']

print(n.parseString("1,00", parseAll=True))
['1', ',', '00']

我也想出了一个正则表达式解决方案,有点晚了:

from pyparsing import Word, nums
import re

n = Word(nums)

def parseNumber(x):
    parseable = re.sub('[,][0-9]{3}', lambda y: y.group()[1:], x)
    return n.parseString(parseable, parseAll=True)

print(parseNumber("1,000,123"))

纯 pyparsing 方法将使用 Combine 来包装一系列代表您在正则表达式中看到的不同字段的 pyparsing 表达式:

import pyparsing as pp

int_with_thousands_separators = pp.Combine(pp.Optional("-") 
                                           + pp.Word(pp.nums, max=3)
                                           + ("," + pp.Word(pp.nums, exact=3))[...])

我发现像这样构建数值表达式会导致解析时间慢得多,因为所有这些单独的部分都是独立解析的,具有多个内部函数和方法调用(这是 [=32= 中真正的性能杀手) ]).因此,您可以使用 Regex:

将其替换为表达式
# more efficient parsing with a Regex
int_with_thousands_separators = pp.Regex(r"-?\d{1,3}(,\d{3})*")

您也可以使用 Jan 发布的代码,并将编译后的正则表达式传递给正则表达式构造函数。

要将解析时转换为 int,请添加去除逗号的解析操作。

# add parse action to convert to int, after stripping ','s
int_with_thousands_separators.addParseAction(
    lambda t: int(t[0].replace(",", "")))

我喜欢使用 runTests 来检查像这样的小表达式 - 编写一系列测试字符串很容易,输出显示解析结果或带有解析失败位置的注释输入字符串。 ("1,00" 作为故意错误包括在内,以证明 runTests 输出的错误。)

int_with_thousands_separators.runTests("""\
    1
    # invalid value
    1,00
    1,000
    -3,000,100
    """)

如果要解析实数,请添加部分以表示尾随小数点和后面的数字。

real_with_thousands_separators = pp.Combine(pp.Optional("-") 
                                           + pp.Word(pp.nums, max=3)
                                           + ("," + pp.Word(pp.nums, exact=3))[...]
                                           + "." + pp.Word(pp.nums))

# more efficient parsing with a Regex
real_with_thousands_separators = pp.Regex(r"-?\d{1,3}(,\d{3})*\.\d+")

# add parse action to convert to float, after stripping ','s
real_with_thousands_separators.addParseAction(
    lambda t: float(t[0].replace(",", "")))

real_with_thousands_separators.runTests("""\
    # invalid values
    1
    1,00
    1,000
    -3,000,100
    1.

    # valid values
    1.732
    -273.15
    """)