【问题标题】:parsing nested structures with pyparsing使用 pyparsing 解析嵌套结构
【发布时间】:2013-10-19 01:40:46
【问题描述】:

我正在尝试解析生物序列中位置的特定语法。职位可以有如下形式:

12           -- a simple position in the sequence
12+34        -- a complex position as a base (12) and offset(+34)
12_56        -- a range, from 12 to 56
12+34_56-78  -- a range as a start to end, where either or both may be simple or complex

我希望将这些解析为字典,大致如下:

12          -> { 'start': { 'base': 12, 'offset': 0 },  'end': None }
12+34       -> { 'start': { 'base': 12, 'offset': 34 }, 'end': None }
12_56       -> { 'start': { 'base': 12, 'offset': 0 },
                   'end': { 'base': 56, 'offset': 0 } }
12+34_56-78 -> { 'start': { 'base': 12, 'offset': 0 }, 
                   'end': { 'base': 56, 'offset': -78 } }

我已经使用 pyparsing 进行了几次测试。这是一个:

from pyparsing import *
integer = Word(nums)
signed_integer = Word('+-', nums)
underscore = Suppress('_')
position = integer.setResultsName('base') + Or(signed_integer,Empty).setResultsName('offset')
interval = position.setResultsName('start') + Or(underscore + position,Empty).setResultsName('end')

结果接近我想要的:

In [20]: hgvspyparsing.interval.parseString('12-34_56+78').asDict()
Out[20]: 
{'base': '56',
'end': (['56', '+78'], {'base': [('56', 0)], 'offset': [((['+78'], {}), 1)]}),
'offset': (['+78'], {}),
'start': (['12', '-34'], {'base': [('12', 0)], 'offset': [((['-34'], {}), 1)]})}

两个问题:

  1. asDict() 仅适用于根 parseResult。有没有办法哄骗 pyparsing 返回一个嵌套的 dict(而且只有那个)?

  2. 如何获得范围结尾的可选性和位置的偏移量?位置规则中的 Or() 并没有削减它。 (我对范围的结尾进行了类似的尝试。)理想情况下,我会将所有位置视为最复杂形式的特殊情况(即 { start: {base, end}, end: { base, end } }),其中更简单的情况使用 0 或 None。)

谢谢!

【问题讨论】:

  • 还有OrAndMatchFirstEach 都将表达式列表作为参数,而不仅仅是表达式。这就是为什么我更喜欢运算符覆盖的原因:expr1 + expr2 | expr3MatchFirst([And([expr1,expr2]), expr3]) 更容易理解。

标签: python grammar pyparsing


【解决方案1】:

一些通用的pyparsing技巧:

Or(expr, empty) 最好写成Optional(expr)。此外,您的 Or 表达式试图用 Empty 类创建 Or,您可能打算为第二个参数编写 Empty()empty

expr.setResultsName("name") 现在可以写成expr("name")

如果您想对结果应用结构,请使用Group

使用dump() 而不是asDict() 可以更好地查看解析结果的结构。

这是我将如何建立你的表达方式:

from pyparsing import Word, nums, oneOf, Combine, Group, Optional

integer = Word(nums)

sign = oneOf("+ -")
signedInteger = Combine(sign + integer)

integerExpr = Group(integer("base") + Optional(signedInteger, default="0")("offset"))

integerRange = integerExpr("start") + Optional('_' + integerExpr("end"))


tests = """\
12
12+34
12_56
12+34_56-78""".splitlines()

for t in tests:
    result = integerRange.parseString(t)
    print t
    print result.dump()
    print result.asDict()
    print result.start.base, result.start.offset
    if result.end:
        print result.end.base, result.end.offset
    print

打印:

12
[['12', '0']]
- start: ['12', '0']
  - base: 12
  - offset: 0
{'start': (['12', '0'], {'base': [('12', 0)], 'offset': [('0', 1)]})}
12 0

12+34
[['12', '+34']]
- start: ['12', '+34']
  - base: 12
  - offset: +34
{'start': (['12', '+34'], {'base': [('12', 0)], 'offset': [('+34', 1)]})}
12 +34

12_56
[['12', '0'], '_', ['56', '0']]
- end: ['56', '0']
  - base: 56
  - offset: 0
- start: ['12', '0']
  - base: 12
  - offset: 0
{'start': (['12', '0'], {'base': [('12', 0)], 'offset': [('0', 1)]}), 'end': (['56', '0'], {'base': [('56', 0)], 'offset': [('0', 1)]})}
12 0
56 0

12+34_56-78
[['12', '+34'], '_', ['56', '-78']]
- end: ['56', '-78']
  - base: 56
  - offset: -78
- start: ['12', '+34']
  - base: 12
  - offset: +34
{'start': (['12', '+34'], {'base': [('12', 0)], 'offset': [('+34', 1)]}), 'end': (['56', '-78'], {'base': [('56', 0)], 'offset': [('-78', 1)]})}
12 +34
56 -78

【讨论】:

    【解决方案2】:

    实际语法是否比您的示例更复杂?因为解析可以在纯 Python 中相当容易地完成:

    bases = ["12", "12+34", "12_56", "12+34", "12+34_56-78"]
    
    def parse_base(base_string):
    
        def parse_single(s):
            if '-' in s:
                offset_start = s.find("-")
                base, offset = int(s[:offset_start]), int(s[offset_start:])
            elif '+' in s:
                offset_start = s.find("+")
                base, offset = int(s[:offset_start]), int(s[offset_start:])
            else:
                base = int(s)
                offset = 0
            return {'base': base, 'offset': offset}
    
        range_split = base_string.split('_')
        if len(range_split) == 1:
            start = range_split[0]
            return {'start': parse_single(start), 'end': None}
        elif len(range_split) == 2:
            start, end = range_split
            return {'start': parse_single(start),
                    'end': parse_single(end)}
    

    输出:

    for b in bases:
         print(parse_base(b))
    
    {'start': {'base': 12, 'offset': 0}, 'end': None}
    {'start': {'base': 12, 'offset': 34}, 'end': None}
    {'start': {'base': 12, 'offset': 0}, 'end': {'base': 56, 'offset': 0}}
    {'start': {'base': 12, 'offset': 34}, 'end': None}
    {'start': {'base': 12, 'offset': 34}, 'end': {'base': 56, 'offset': -78}}
    

    【讨论】:

    • 是的,还有很多其他的解析工作要做,而且位置问题让我受不了。 Paul McGuire 的解决方案更符合我的要求。
    • 干得好! +1 使用所有字符串函数,无需使用正则表达式。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-13
    相关资源
    最近更新 更多