从一串多个标记构建 pyparsing.Dict - 第二部分
building a pyparsing.Dict from a string of multiple tokens - part II
感谢论坛的反馈,我取得了一些进步(感谢论坛!)。
pyparsing.Dict 对象字典正在填充,但在找到十进制数字时静默失败。
给定:
import pyparsing as pp
lines = '''\
(rate multiple)
(region "mountainous")
(elev 21439)
(alteleva +21439)
(altelevb -21439)
(coorda 23899.747)
(coordb +23899.747)
(coordc -23899.747)
(coordd 853.324e21)
(coorde +853.324e21)
(coordf -853.324e21)
(coordg 987.88e+09)
(coordh +987.88e+09)
(coordi -987.88e+09)
(coordj 122.45e-04)
(coordk +122.45e-04)
(coordl -122.45e-04)
'''
leftParen = pp.Literal('(')
rightParen = pp.Literal(')')
colon = pp.Literal(':')
decimalpoint = pp.Literal('.')
doublequote = pp.Literal('"')
plusorminus = pp.Literal('+') | pp.Literal('-')
exp = pp.CaselessLiteral('E')
v_string = pp.Word(pp.alphanums)
v_quoted_string = pp.Combine( doublequote + v_string + doublequote)
v_number = pp.Regex(r'[+-]?(?P<float1>\d+)(?P<float2>\.\d+)?(?P<float3>[Ee][+-]?\d+)?')
keyy = v_string
valu = v_string | v_quoted_string | v_number
item = pp.Group( pp.Literal('(').suppress() + keyy + valu + pp.Literal(')').suppress() )
items = pp.ZeroOrMore( item)
dict = pp.Dict( items)
print "dict yields: ", dict.parseString( lines).dump()
产量
- alteleva: '+21439',
- altelevb: '-21439',
- elev: '21439',
- rate: 'multiple',
- region: '"mountainous"'
改变标记的顺序证明脚本在遇到第一个十进制数时静默失败,这意味着 pp.Regex 语句有一些微妙的错误,但我肯定无法发现它。
TIA,
code_warrior
你的问题其实出在这个表达式上:
valu = v_string | v_quoted_string | v_number
因为 v_string
被定义为 broadly-matching 表达式:
v_string = pp.Word(pp.alphanums)
并且因为它是 valu
中的第一个表达式,它将屏蔽以数字开头的 v_numbers
。这是因为 '|'运算符生成 pp.MatchFirst
个对象,因此第一个匹配的表达式(读取 left-to-right)将决定使用哪个替代项。您可以转换为使用生成 pp.Or
对象的“^”运算符 - Or
class 将尝试评估所有备选方案,然后使用 longest匹配。但是,请注意,使用 Or
会带来性能损失,因为即使没有混淆的机会,也会有更多的表达式来测试匹配项。在您的情况下,您可以重新排序表达式以将最不具体的匹配表达式放在最后:
valu = v_quoted_string | v_number | v_string
现在将首先尝试将值解析为带引号的字符串,然后解析为数字,然后仅当这些特定类型中的任何一种都不匹配时,才解析为非常通用的类型 v_string
.
其他一些评论:
我个人更喜欢解析带引号的字符串,只获取引号内的内容(这是一个字符串,我已经知道了!)。当显示解析的字符串时没有任何引号引起来时,在转储解析结果时,旧版本的 pyparsing 曾经存在一些混淆。但是现在我使用 repr() 来显示解析后的值,字符串在调用 dump()
时显示在引号中,但值本身不包括引号。当它在程序的其他地方使用时,例如保存到数据库或 CSV,我不需要引号,我只需要字符串内容。默认情况下,QuotedString
class 会帮我解决这个问题。或者使用 pp.quotedString().addParseAction(pp.removeQuotes)
.
最近的 pyparsing 版本引入了 pyparsing_common
命名空间 class,其中包含许多有用的 pre-defined 表达式。有几个用于解析不同的数字类型(整数、有符号整数、实数等),以及几个笼统的表达式:number
将解析任何数字类型,并生成相应类型的值(real
会给出一个浮点数,integer
会给出一个整数,等等); fnumber
将解析各种数字,但 return 它们都是浮点数。我已经用 pp.pyparsing_common.number()
替换了你的 v_number
表达式,这也允许我删除其他几个为构建 v_number
表达式而定义的部分表达式,比如 decimalpoint
, plusorminus
和 exp
。您可以在在线文档中查看有关 pyparsing_common
中表达式的更多信息:https://pythonhosted.org/pyparsing/
Pyparsing 在像 "(" + pp.Word(pp.alphas) + valu + ")"
这样的表达式中处理文字字符串时的默认行为是自动将文字“(”和“)”术语转换为 pp.Literal
对象。这可以防止意外丢失已解析的数据,但在标点符号的情况下,您最终会在解析结果中得到许多混乱且无用的额外字符串。在您的解析器中,您可以通过调用 pp.ParserElement.inlineLiteralsUsing
并传递 pp.Suppress
class:
来替换 pyparsing 的默认值
pp.ParserElement.inlineLiteralsUsing(pp.Suppress)
现在你可以写这样的表达式:
item = pp.Group('(' + keyy + valu + ')')
分组括号将从解析结果中隐藏。
进行这些更改后,您的解析器现在可简化为:
import pyparsing as pp
# override pyparsing default to suppress literal strings in expressions
pp.ParserElement.inlineLiteralsUsing(pp.Suppress)
v_string = pp.Word(pp.alphanums)
v_quoted_string = pp.QuotedString('"')
v_number = pp.pyparsing_common.number()
keyy = v_string
# define valu using least specific expressions last
valu = v_quoted_string | v_number | v_string
item = pp.Group('(' + keyy + valu + ')')
items = pp.ZeroOrMore( item)
dict_expr = pp.Dict( items)
print ("dict yields: ", dict_expr.parseString( lines).dump())
对于您的测试输入,给出:
dict yields: [['rate', 'multiple'], ['region', 'mountainous'], ['elev', 21439],
['alteleva', 21439], ['altelevb', -21439], ['coorda', 23899.747], ['coordb',
23899.747], ['coordc', -23899.747], ['coordd', 8.53324e+23], ['coorde',
8.53324e+23], ['coordf', -8.53324e+23], ['coordg', 987880000000.0], ['coordh',
987880000000.0], ['coordi', -987880000000.0], ['coordj', 0.012245], ['coordk',
0.012245], ['coordl', -0.012245]]
- alteleva: 21439
- altelevb: -21439
- coorda: 23899.747
- coordb: 23899.747
- coordc: -23899.747
- coordd: 8.53324e+23
- coorde: 8.53324e+23
- coordf: -8.53324e+23
- coordg: 987880000000.0
- coordh: 987880000000.0
- coordi: -987880000000.0
- coordj: 0.012245
- coordk: 0.012245
- coordl: -0.012245
- elev: 21439
- rate: 'multiple'
- region: 'mountainous'
感谢论坛的反馈,我取得了一些进步(感谢论坛!)。 pyparsing.Dict 对象字典正在填充,但在找到十进制数字时静默失败。
给定:
import pyparsing as pp
lines = '''\
(rate multiple)
(region "mountainous")
(elev 21439)
(alteleva +21439)
(altelevb -21439)
(coorda 23899.747)
(coordb +23899.747)
(coordc -23899.747)
(coordd 853.324e21)
(coorde +853.324e21)
(coordf -853.324e21)
(coordg 987.88e+09)
(coordh +987.88e+09)
(coordi -987.88e+09)
(coordj 122.45e-04)
(coordk +122.45e-04)
(coordl -122.45e-04)
'''
leftParen = pp.Literal('(')
rightParen = pp.Literal(')')
colon = pp.Literal(':')
decimalpoint = pp.Literal('.')
doublequote = pp.Literal('"')
plusorminus = pp.Literal('+') | pp.Literal('-')
exp = pp.CaselessLiteral('E')
v_string = pp.Word(pp.alphanums)
v_quoted_string = pp.Combine( doublequote + v_string + doublequote)
v_number = pp.Regex(r'[+-]?(?P<float1>\d+)(?P<float2>\.\d+)?(?P<float3>[Ee][+-]?\d+)?')
keyy = v_string
valu = v_string | v_quoted_string | v_number
item = pp.Group( pp.Literal('(').suppress() + keyy + valu + pp.Literal(')').suppress() )
items = pp.ZeroOrMore( item)
dict = pp.Dict( items)
print "dict yields: ", dict.parseString( lines).dump()
产量
- alteleva: '+21439',
- altelevb: '-21439',
- elev: '21439',
- rate: 'multiple',
- region: '"mountainous"'
改变标记的顺序证明脚本在遇到第一个十进制数时静默失败,这意味着 pp.Regex 语句有一些微妙的错误,但我肯定无法发现它。
TIA,
code_warrior
你的问题其实出在这个表达式上:
valu = v_string | v_quoted_string | v_number
因为 v_string
被定义为 broadly-matching 表达式:
v_string = pp.Word(pp.alphanums)
并且因为它是 valu
中的第一个表达式,它将屏蔽以数字开头的 v_numbers
。这是因为 '|'运算符生成 pp.MatchFirst
个对象,因此第一个匹配的表达式(读取 left-to-right)将决定使用哪个替代项。您可以转换为使用生成 pp.Or
对象的“^”运算符 - Or
class 将尝试评估所有备选方案,然后使用 longest匹配。但是,请注意,使用 Or
会带来性能损失,因为即使没有混淆的机会,也会有更多的表达式来测试匹配项。在您的情况下,您可以重新排序表达式以将最不具体的匹配表达式放在最后:
valu = v_quoted_string | v_number | v_string
现在将首先尝试将值解析为带引号的字符串,然后解析为数字,然后仅当这些特定类型中的任何一种都不匹配时,才解析为非常通用的类型 v_string
.
其他一些评论:
我个人更喜欢解析带引号的字符串,只获取引号内的内容(这是一个字符串,我已经知道了!)。当显示解析的字符串时没有任何引号引起来时,在转储解析结果时,旧版本的 pyparsing 曾经存在一些混淆。但是现在我使用 repr() 来显示解析后的值,字符串在调用 dump()
时显示在引号中,但值本身不包括引号。当它在程序的其他地方使用时,例如保存到数据库或 CSV,我不需要引号,我只需要字符串内容。默认情况下,QuotedString
class 会帮我解决这个问题。或者使用 pp.quotedString().addParseAction(pp.removeQuotes)
.
最近的 pyparsing 版本引入了 pyparsing_common
命名空间 class,其中包含许多有用的 pre-defined 表达式。有几个用于解析不同的数字类型(整数、有符号整数、实数等),以及几个笼统的表达式:number
将解析任何数字类型,并生成相应类型的值(real
会给出一个浮点数,integer
会给出一个整数,等等); fnumber
将解析各种数字,但 return 它们都是浮点数。我已经用 pp.pyparsing_common.number()
替换了你的 v_number
表达式,这也允许我删除其他几个为构建 v_number
表达式而定义的部分表达式,比如 decimalpoint
, plusorminus
和 exp
。您可以在在线文档中查看有关 pyparsing_common
中表达式的更多信息:https://pythonhosted.org/pyparsing/
Pyparsing 在像 "(" + pp.Word(pp.alphas) + valu + ")"
这样的表达式中处理文字字符串时的默认行为是自动将文字“(”和“)”术语转换为 pp.Literal
对象。这可以防止意外丢失已解析的数据,但在标点符号的情况下,您最终会在解析结果中得到许多混乱且无用的额外字符串。在您的解析器中,您可以通过调用 pp.ParserElement.inlineLiteralsUsing
并传递 pp.Suppress
class:
pp.ParserElement.inlineLiteralsUsing(pp.Suppress)
现在你可以写这样的表达式:
item = pp.Group('(' + keyy + valu + ')')
分组括号将从解析结果中隐藏。
进行这些更改后,您的解析器现在可简化为:
import pyparsing as pp
# override pyparsing default to suppress literal strings in expressions
pp.ParserElement.inlineLiteralsUsing(pp.Suppress)
v_string = pp.Word(pp.alphanums)
v_quoted_string = pp.QuotedString('"')
v_number = pp.pyparsing_common.number()
keyy = v_string
# define valu using least specific expressions last
valu = v_quoted_string | v_number | v_string
item = pp.Group('(' + keyy + valu + ')')
items = pp.ZeroOrMore( item)
dict_expr = pp.Dict( items)
print ("dict yields: ", dict_expr.parseString( lines).dump())
对于您的测试输入,给出:
dict yields: [['rate', 'multiple'], ['region', 'mountainous'], ['elev', 21439],
['alteleva', 21439], ['altelevb', -21439], ['coorda', 23899.747], ['coordb',
23899.747], ['coordc', -23899.747], ['coordd', 8.53324e+23], ['coorde',
8.53324e+23], ['coordf', -8.53324e+23], ['coordg', 987880000000.0], ['coordh',
987880000000.0], ['coordi', -987880000000.0], ['coordj', 0.012245], ['coordk',
0.012245], ['coordl', -0.012245]]
- alteleva: 21439
- altelevb: -21439
- coorda: 23899.747
- coordb: 23899.747
- coordc: -23899.747
- coordd: 8.53324e+23
- coorde: 8.53324e+23
- coordf: -8.53324e+23
- coordg: 987880000000.0
- coordh: 987880000000.0
- coordi: -987880000000.0
- coordj: 0.012245
- coordk: 0.012245
- coordl: -0.012245
- elev: 21439
- rate: 'multiple'
- region: 'mountainous'