eslint代码长度超过了最大值限制_pyparsing资料翻译(05-20:发现超过长度限制了,不再更新)...

c6f4013f1bffc7d345d654fa4c34078c.png

在尝试解析SQL语句的过程中,查过pyparsing的资料,感觉中文资源还不够丰富,打算翻译一篇介绍pyparsing的文章,争取每天翻译一点:

http://infohost.nmt.edu/tcc/help/pubs/pyparsing/pyparsing.pdf​infohost.nmt.edu

Tip:

1、python还有个叫pyparse的库,似乎是心理学领域使用的解析音频文件的工具,不要搞混了。

2、本文有七部分组成,前四章为功能介绍,后三章为参考手册,第五章为类(Class)的介绍,第六章为函数的介绍,第七章为变量的介绍。

3、关于setParseAction函数。如果用pyparsing的目标,不只是提取字符串出来,而是,比如说,要把某个子parser提取出来的字符串转换成数据库的table名、字段名,可能就会用到setParseAction,在解析的过程中,实现从字符串到table name的转换。

这是stackoverflow上的一个例子:

https://stackoverflow.com/questions/2941029/pyparsing-is-this-correct-use-of-setparseaction​stackoverflow.com

1、pyparsing:从文本(text)中提取信息的工具

pyparsing模块的用途,是为python程序员提供一个工具,去提取结构化文本数据中的信息。

从功能上说,这个模块要比python自带的正则表达式更强大(re),又不像全功能的编译器那样通用。

为了在结构化文本(structured text)中查找信息,就要有定义结构的方法。pyparsing模块用巴科斯范式,或者叫BNF,来定义语法结构。熟悉基于BNF的各种语法符号,会有助于使用pyparsing模块。

Pyparsing 模块匹配输入文本中的模式,使用的是递归下降解析器: 我们编写类似BNF 的语法定义,pyparsing提供匹配这些语法定义的自动机。

待分析文本的语法结构,如果能被准确定义,那pyparsing就会工作在最佳状态。一个常见的pyparsing应用是分析日志文件。日志文件通常具有固定的结构,包括日期、IP地址等字段。本文不涉及自然语言处理中,可能用到pyparsing的场景。

有用的在线参考包括:

pyparsing的主页:pyparsing/pyparsing

完整的在线参考手册,包括类、函数、变量的说明:pyparsing/pyparsing

...

pyparsing作者2004年写的指南,虽然稍微过时,但仍然有用:Using the pyparsing module

使用pyparsing的一个小示例(大约10个语法结构),请参考icalparse:icalparse: A pyparsing parser for .calendar files

使用pyparsing的一个适度的示例,《abaras:A shorthand notation for bird records》,这个例子有大约30个语法结构。实际的实现在一份单独的文档中描述,《abaraw: internal maintenance specification》,基本上,这是个用pyparsing实现的内核,附加了一些转换成XML的应用逻辑,以便后继流程继续处理。链接1:abaraw: A shorthand notation for bird records 链接2:abaraw internal maintenance specification

2、构建您的应用

简单概括下用pyparsing模块写程序的一般步骤:

1编写BNF,描述待分析文本的结构。

2如果需要,安装pyparsing模块。大部分最近的python安装会自带该模块,如果你的环境上没有pyparsing,请从pyparsing主页下载(译注:推荐用pip install pyparsing)。

3在你的python代码中,import pyparsing模块。我们推荐采用这样的形式:

import pyparsing as pp

本文中的例子将用pp指代pyparsing。

4您的脚本会组装一个和BNF定义匹配的parser,parser是抽象基类pp.ParserElement的实例,pp.ParserElement用于描述一般模式。

为输入文件格式构建解析器是一个自下而上的过程。 首先为最小的部分编写解析器,然后将它们组装成越来越大的部分,然后为整个文件提供解析器(这句是从google翻译直接拷贝过来的^_^)。

5构建包含要处理的输入文本的Python字符串(类型为str或unicode)。

6如果parser是p,输入文本是s,则下面的代码将尝试匹配它们:

p.parseString(s)

如果s与p描述的语法匹配,上述代码将返回一个对象,这个对象是类pp.ParseResults的一个实例,代表了匹配到的部分。

如果s与您的parser不匹配,会触发类pp.ParseException的异常。

此异常会标明,输入字符串的哪里没能被匹配到。

.parseString()方法依次处理输入文本,用您定义的parser匹配文本块。有时,最底层的parser称为token,更高层的被称为模式(pattern)。

您可以将解析操作附加到任何组件parser。 例如,整数的parser可能具有附加的解析操作,该操作将字符串转换为Python int。

7返回的ParseResults实例中,包含应用程序的信息。 该实例的确切结构取决于您构建解析器的方式。

3、一个小而完整的例子

下面这个能“跑”的小例子,只是为了给读者一个大致的印象,知道pyparsing怎么用。

Python标识符的名称,由一个或多个字符构成,其中第一个字符是字母或者下划线("_"),后面可以接字母、数字、下划线。在扩展BNF中,我们可以这样写:

first ::= letter | "_"
letter ::= "a" | "b" | ... "z" | "A" | "B" | ... | "Z"
digit ::= "0" | "1" | ... | "9"
rest ::= first | digit
identifier ::= first rest*

最后一条规则(production,常见的译法是“产生式”),可以理解为:“标识符由一个first和零个或多个rest组成”。

下面是实现该语法的脚本,并用一些字符串对脚本进行测试。

#!/usr/bin/env python
#================================================================
# trivex: Trivial example
#----------------------------------------------------------------
# - - - - - I m p o r t s
import sys

下一行导入pyparsing模块并将其重命名为pp。

import pyparsing as pp
# - - - - - M a n i f e s t c o n s t a n t s

在下一行中,pp.alphas变量是一个包含所有小写和大写字母的字符串。 pp.Word()类生成一个解析器,该解析器匹配由其第一个参数定义的字符串; 参数exact = 1告诉解析器(parser)接受该字符串中的一个字符。 first是一个解析器(即ParserElement实例),它只匹配一个字母或一个下划线。

first = pp.Word(pp.alphas+"_", exact=1)

pp.alphanums变量是一个包含所有字母和所有数字的字符串。 因此,rest模式匹配一个或多个字母,数字或下划线。

rest = pp.Word(pp.alphanums+"_")

Python“+”运算符被pp.ParserElement重载,以表示序列:即,标识符解析器匹配first解析器匹配的内容,后跟可选的rest解析器匹配的内容。

identifier = first+pp.Optional(rest)
testList = [ # List of test strings
# Valid identifiers
"a", "foo", "_", "Z04", "_bride_of_mothra",
# Not valid
"", "1", "$*", "a_#" ]
# - - - - - m a i n
def main():
    """
    """
    for text in testList:
        test(text)

# - - - t e s t
def test(s):
    '''See if s matches identifier.
    '''
    print "---Test for '{0}'".format(s)

当您在pp.ParserElement类的实例上调用.parseString()方法时,它会返回匹配元素的列表,或触发pp.ParseException异常。

    try:
        result = identifier.parseString(s)
        print " Matches: {0}".format(result)
    except pp.ParseException as x:
        print " No match: {0}".format(str(x))
        # - - - - - E p i l o g u e
if __name__ == "__main__":
    main()

下面是脚本的输出:

---Test for 'a'
Matches: ['a']
6 pyparsing quick reference New Mexico Tech Computer Center
---Test for 'foo'
Matches: ['f', 'oo']
---Test for '_'
Matches: ['_']
---Test for 'Z04'
Matches: ['Z', '04']
---Test for '_bride_of_mothra'
Matches: ['_', 'bride_of_mothra']
---Test for ''
No match: Expected W:(abcd...) (at char 0), (line:1, col:1)
---Test for '1'
No match: Expected W:(abcd...) (at char 0), (line:1, col:1)
---Test for '$*'
No match: Expected W:(abcd...) (at char 0), (line:1, col:1)
---Test for 'a_#'
Matches: ['a', '_']

返回值是pp.ParseResults类的一个实例; 打印时,它显示为匹配字符串的列表。 你会注意到,对于单个字符串,结果列表只有一个元素,而对于多字母字符串,列表有两个元素:第一个字符(first规则匹配的部分)和rest规则匹配到的其余字符。

如果想让结果列表只包含一个元素,需要改动一行代码:

identifier = pp.Combine(first+pp.Optional(rest))

pp.Combine【1】类让pyparsing把参数列表的匹配片段,放到一个单独的结果中。以下两行是修改脚本后的示例:

---Test for '_bride_of_mothra'
Matches: ['_bride_of_mothra']

4、如何构造返回的ParseResults

当您的输入与您构建的解析器匹配时,.parseString()方法返回类ParseResults的实例。

对于复杂的结构,输入中的不同部分,在ParseResult实例中会有不同bits与之对应。 ParseResults实例内部的确切结构,取决于您如何构建顶层解析器。

生成的ParseResult实例,可以通过两种方式访问:

  • 作为list。匹配到n个内部组件的解析器(parser),它返回的结果r,可以被当作有n个字段串的list访问。可以用r[n]访问第n个元素;或者,可以用list()函数,将结果转换成真正的list。
>>> import pyparsing as pp
>>> number = pp.Word(pp.nums)
>>> result = number.parseString('17')
>>> print result
['17']
>>> type(result)
<class 'pyparsing.ParseResults'>
>>> result[0]
'17'
>>> list(result)
['17']
>>> numberList = pp.OneOrMore(number)
>>> print numberList.parseString('17 33 88')
['17', '33', '88']
  • 作为字典。您可以通过调用其.setResultsName(s)方法将结果名称r附加到解析器(请参见第5.1节“ParserElement:基本解析器构建块”(第11页))。 完成后,您可以从ParseResults实例r中提取匹配的字符串作为“r [s]”。
>>> number = pp.Word(pp.nums).setResultsName('nVache')
>>> result = number.parseString('17')
>>> print result
['17']
>>> result['nVache']
'17

以上是构造解析器的ParseResults实例的一些通用原则。

4.1、用pp.Group()分而治之

和任何复杂点的程序一样,分而治之的原则对构造解析器也是适用的,对任意复杂度的解析器,分而治之(或者称之为逐步细化),能让构造解析器的过程变得更容易。

实践中,顶层的ParseResults不应包含过多元素,比如说,不能超过五个或者七个。如果这一层的元素过多,请查看总体输入,并将其划分成两个或更多的子解析器。然后再重新构建顶层解析器,让它只包含这些片段。如果有必要,将子解析器再划分为更小的解析器,直到每一个解析器,或者由内置的基本函数构成,或者由其他解析器构成。

第5.13节,“Group:将重复的项目组合成一个列表”(第23页)是创建这些抽象级别的基本工具。

  • 通常,匹配到多个内容的解析器,它的ParseResults实例会表现得像一个,由被匹配到的字符串组成的列表。例如,如果解析器匹配一个单词列表,它的ParseResults,可以像列表一样被打印(print)出来。使用type()函数,我们能看到列表中每个元素的实际类型,是python的字符串。
>>> word = pp.Word(pp.alphas)
>>> phrase = pp.OneOrMore(word)
>>> result = phrase.parseString('farcical aquatic ceremony')
>>> print result
['farcical', 'aquatic', 'ceremony']
>>> type(result)
<class 'pyparsing.ParseResults'>
>>> type(result[0])
<type 'str'>
  • 但是,对解析器的定义包含pp.Group()时,它的pp.ParseResults中的每个元素,表现得像是列表(译注:没看懂这句话的字面意思,不过,看实例代码能明白Group的效果,被Group匹配到的部分,是ParseResults中的一个单独的元素。)。

例如,假设您的程序正在拆解一系列单词,并且您希望以一种方式处理第一个单词,而将另一个单词处理为另一种方式。 这是我们的第一次尝试。

>>> ungrouped = word + phrase
>>> result = ungrouped.parseString('imaginary farcical aquatic ceremony')
>>> print result
['imaginary', 'farcical', 'aquatic', 'ceremony']

这个结果与我们的概念并不完全一致,即解析器是两个事物的序列:单个单词,后跟一系列单词。

在解析器的定义中增加pp.Group(),它将返回与我们的概念匹配的两个事物的序列。

>>> grouped = word + pp.Group(phrase)
>>> result = grouped.parseString('imaginary farcical aquatic ceremony')
>>> print result
['imaginary', ['farcical', 'aquatic', 'ceremony']]
>>> print result[1]
['farcical', 'aquatic', 'ceremony']
>>> type(result[1])
<class 'pyparsing.ParseResults'>
>>> result[1][0]
'farcical'
>>> type(result[1][0])
<type 'str'>

1包含分组的解析器,由两个部分构成,一个word和一个pp.Group。因此返回的结果就像是一个双元素的列表。

2第一个元素是实际的字符串,'imaginary'。

3第二部分是另一个pp.ParseResults实例,其作用类似于字符串列表。

因此对于较大的语法,顶级解析器在匹配时返回的pp.ParseResults实例,通常是混合的多层结构,既包含这种普通字符串,也包含其他pp.ParseResults实例。 下一节将为您提供有关管理这些beast结构的一些建议(译注:beast在这里是俚语么?直接翻译成野兽实在太突兀了)。

4.2、使用结果名称进行结构化

对于仅在特定级别出现一次的解析器,请考虑使用.setResultsName()将结果名称与该解析器相关联。这样,就可以把ParseResults看成是字典,用名称作为关键字从字典中获取匹配到的文本。

此选项的设计规则:

  • 通过名称访问比通过位置访问更robust。您正在处理的结构,可能会随时间而改变。以列表的方式访问结果,底层的结构变了,一个元素对应的位置可能会随之改变。

但是,如果您给结果命名为'swamp Name',则访问代码result['swamp Name']可能仍然会继续工作,即使后来result中又增添了其他名称。

  • 在一个ParseResults中,按位置访问或者按关键字访问(即按结果名称),不可得兼。 如果解析器的某些子元素被命名了,而另一些子元素没有被命名,则所有这些子元素的匹配文本,将在结果中混合在一起。

下面是一个示例,显示当您在同一级别混合位置和命名访问时会发生什么:在骑牛比赛中,总分是骑手的得分和公牛的得分的组合。

>>> rider = pp.Word(pp.alphas).setResultsName('Rider')
>>> bull = pp.Word(pp.alphas).setResultsName('Bull')
>>> score = pp.Word(pp.nums+'.')
>>> line = rider + score + bull + score
>>> result = line.parseString('Mauney 46.5 Asteroid 46')
>>> print result
['Mauney', '46.5', 'Asteroid', '46']

在上面显示的四元素列表中,您可以按名称访问第一个和第三个元素,但只能按位置访问第二个和第四个元素。

一种更合理的方法,是创建由名字和分数组合而成的解析器,然后,再用两个这样的解析器

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值