Python pyparsing-使用千个分隔符分析数字

Python pyparsing-使用千个分隔符分析数字,python,python-3.x,pyparsing,Python,Python 3.x,Pyparsing,所以我在做一个解析器,我注意到了一个问题。事实上,要分析数字,我有: 从pyparsing导入单词,nums n=单词(nums) 这适用于没有数千个分隔符的数字。例如,n.parseString(“1000”,parseAll=True)返回(['1000'],{}),因此可以工作 但是,当我添加千分隔符时,它不起作用。实际上,n.parseString(“1000”,parseAll=True)引发了pyparsing.ParseException:预期的文本结尾,找到“,”(在字符1处)

所以我在做一个解析器,我注意到了一个问题。事实上,要分析数字,我有:

从pyparsing导入单词,nums
n=单词(nums)
这适用于没有数千个分隔符的数字。例如,
n.parseString(“1000”,parseAll=True)
返回
(['1000'],{})
,因此可以工作

但是,当我添加千分隔符时,它不起作用。实际上,
n.parseString(“1000”,parseAll=True)
引发了
pyparsing.ParseException:预期的文本结尾,找到“,”(在字符1处),(第1行,第2列)


如何使用千个分隔符解析数字?我不想忽略逗号(例如,
n.parseString(“1,00”,parseAll=True)
应该返回一个错误,因为它不是一个数字)。

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

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
将导致
不是一个数字
,请参阅上的表达式演示。

我不太理解你所说的“带有数千个分隔符的数字”的意思

在任何情况下,使用pyparsing,您都应该定义要解析的模式

在第一个示例中,pyparse工作得很好,因为您将n定义为一个数字,所以:

n = Word(nums)
print(n.parseString("1000", parseAll=True))
['1000']
因此,如果要解析“1000”或“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))[...])
我发现,构建这样的数值表达式会导致解析时间大大降低,因为所有这些独立的部分都是独立解析的,有多个内部函数和方法调用(这在Python中是真正的性能杀手)。因此,您可以使用
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”
作为故意错误包括在内,以演示运行测试的错误输出)

如果要分析实数,请添加表示尾随小数点和后面数字的片段

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
    """)

那不是我想要的。我不想解析
1,00
,因为它不是一个数字。此外,这只适用于仅带1000分隔符的数字,但不适用于例如
1000000
,因为此数字有2000个分隔符separators@TheOneMusic:很高兴能帮上忙。事实上,我自己从来没有使用过
pyparsing
,我最喜欢的是。如果你去掉“^”和“$”锚点(如果扫描一个较大表达式中的数字,这可能会混淆pyparsing),你可以用这个re来构建一个pyparsing正则表达式,然后把它合并成一个更大的表达式。@PaulMcG:我知道你是
pyparsing
的真正作者。请随意编辑我的答案或自己提供一个答案,以包含您的解决方案。我不想从您那里窃取您的检查点-我始终感谢并鼓励其他人参与到问题中来。我写pyparsing是因为我对正则表达式的理解很糟糕,但就在此后的19年里,我实际上在正则表达式方面做得相当好。@PaulMcG:这不是因为名声,而是因为学习,不是吗。我甚至可以和-15 rep.@Jan一起睡个好觉。在这种情况下,这意味着要提出一个例外。这是一个无效的号码。阅读操作。传递给
runTests
的一些示例字符串(例如
“1,00”
,或不带小数部分的实数)是故意错误,以演示
runTests
错误输出。我已经编辑以显示预期失败的示例字符串。
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
    """)