pyparsing-解析包含千个分隔符的数字

时间:2020-07-18 10:38:15

标签: python python-3.x pyparsing

所以我在做一个解析器,发现一个问题。确实,要解析数字,我有:

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

这对于没有数千个分隔符的数字非常有效。例如,n.parseString("1000", parseAll=True)返回(['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)应该返回错误,因为它不是数字)。

4 个答案:

答案 0 :(得分:2)

首先处理字符串时,可以很好地在其上使用正则表达式以确保它确实是一个数字(包括数千个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上的表达式演示。

答案 1 :(得分:2)

一种纯粹的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))[...])

我发现建立这样的数字表达式会导致解析时间大大缩短,因为所有这些单独的部分都是使用多个内部函数和方法调用(在Python中是真正的性能杀手)进行独立解析。因此,您可以使用Regex将其替换为表达式:

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

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

要将解析时间转换为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
    """)

答案 2 :(得分:0)

我不太理解“带数千个分隔符的数字”的意思。

无论如何,使用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']

答案 3 :(得分:0)

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

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"))
相关问题