pyparsing 问题

1 投票
3 回答
979 浏览
提问于 2025-04-15 18:57

这段代码可以正常运行:

from pyparsing import *

zipRE = "\d{5}(?:[-\s]\d{4})?" 
fooRE = "^\!\s+.*"

zipcode = Regex( zipRE )
foo = Regex( fooRE )

query = ( zipcode | foo )



tests = [ "80517", "C6H5OH", "90001-3234", "! sfs" ]

for t in tests:
    try:
        results = query.parseString( t )
        print t,"->", results
    except ParseException, pe:
        print pe

我遇到了两个问题:

1 - 如何使用自定义函数来解析一个标记。比如说,如果我想用一些自定义的逻辑,而不是正则表达式,来判断一个数字是否是邮政编码。

不是这样:

zipcode = Regex( zipRE )

而是这样:

zipcode = MyFunc()

2 - 我怎么知道一个字符串解析成什么。比如“80001”解析成“邮政编码”,但我怎么用pyparsing来判断这一点?我并不是在解析字符串的内容,而是单纯想知道它是什么类型的查询。

3 个回答

2

我没有安装 pyparsing 这个模块,但 Regex 应该是一个类,而不是一个函数。

你可以从这个类继承一个新类,然后根据需要重写一些方法来定制它的行为,最后使用你自己创建的新类。

3

你可以把邮政编码和其他内容分开使用,这样你就能知道字符串是匹配哪个部分的。

zipresults = zipcode.parseString( t )
fooresults = foo.parseString( t )
2

你的第二个问题很简单,我先回答这个。把查询改成给不同的表达式命名,这样结果就更清晰了:

query = ( zipcode("zip") | foo("foo") ) 

现在你可以在返回的结果上调用 getName() 方法:

print t,"->", results, results.getName()

这样会得到:

80517 -> ['80517'] zip
Expected Re:('\\d{5}(?:[-\\s]\\d{4})?') (at char 0), (line:1, col:1)
90001-3234 -> ['90001-3234'] zip
! sfs -> ['! sfs'] foo

如果你打算用结果中的 fooness 或 zipness 来调用另一个函数,那么你可以在解析时通过给 foo 和 zipcode 表达式附加一个解析动作来实现:

# enclose zipcodes in '*'s, foos in '#'s
zipcode.setParseAction(lambda t: '*' + t[0] + '*')
foo.setParseAction(lambda t: '#' + t[0] + '#')

query = ( zipcode("zip") | foo("foo") ) 

现在会得到:

80517 -> ['*80517*'] zip
Expected Re:('\\d{5}(?:[-\\s]\\d{4})?') (at char 0), (line:1, col:1)
90001-3234 -> ['*90001-3234*'] zip
! sfs -> ['#! sfs#'] foo

关于你的第一个问题,我不太清楚你指的是什么样的函数。Pyparsing 提供了比正则表达式更多的解析类,比如 Word、Keyword、Literal 和 CaselessLiteral,你可以用 '+', '|', '^', '~', '@' 和 '*' 这些操作符来组合你的解析器。例如,如果你想解析美国社会安全号码,但不想用正则表达式,你可以这样做:

ssn = Combine(Word(nums,exact=3) + '-' + 
        Word(nums,exact=2) + '-' + Word(nums,exact=4))

Word 用来匹配由构造函数中给定字符组成的连续“单词”,Combine 则把匹配到的标记合并成一个单一的标记。

如果你想解析一个可能的这样的号码列表,用 '/' 分隔,可以这样写:

delimitedList(ssn, '/')

或者如果这些号码在 1 到 3 个之间,没有分隔符,可以这样写:

ssn * (1,3)

任何表达式都可以附加结果名称或解析动作,以进一步丰富解析结果或在解析时的功能。你甚至可以使用 Forward 类构建递归解析器,比如嵌套的括号列表、算术表达式等。

我在写 pyparsing 时的初衷是希望通过基本构建块组合解析器成为创建解析器的主要方式。后来我才添加了正则表达式,作为我认为的“终极逃生阀”——如果人们无法构建自己的解析器,他们可以退而求其次使用正则表达式的格式,这种格式随着时间的推移确实证明了它的强大。

或者,正如其他人建议的,你可以打开 pyparsing 的源代码,继承现有类,或者按照它们的结构自己写一个新的类。这里有一个类可以匹配成对的字符:

class PairOf(Token):
    """Token for matching words composed of a pair
       of characters in a given set.
    """
    def __init__( self, chars ):
        super(PairOf,self).__init__()
        self.pair_chars = set(chars)

    def parseImpl( self, instring, loc, doActions=True ):
        if (loc < len(instring)-1 and 
           instring[loc] in self.pair_chars and
           instring[loc+1] == instring[loc]):
            return loc+2, instring[loc:loc+2]
        else:
            raise ParseException(instring, loc, "Not at a pair of characters")

这样:

punc = r"~!@#$%^&*_-+=|\?/"
parser = OneOrMore(Word(alphas) | PairOf(punc))
print parser.parseString("Does ** this match @@@@ %% the parser?")

会得到:

['Does', '**', 'this', 'match', '@@', '@@', '%%', 'the', 'parser']

(注意最后的单个 '?' 被省略了)

撰写回答