python:Python 编写的强大的、通用的解析器




  Spark 是种用 Python 编写强大、通用解析器/编译器框架在某些方面Spark 所提供比 SimpleParse 或其它 Python 解析器提供都要多然而它完全是用 Python 编写所以速度也会比较慢David 在本文中讨论了 Spark 模块给出了些代码样本解释了它用途并对其应用领域提供了些建议
  
  继“可爱 Python”系列中专门讲述 SimpleParse 篇文章的后我将在本文中继续介绍些解析基本概念并对 Spark 模块进行了讨论解析框架是个内容丰富主题它值得我们多花时间去全面了解;这两篇文章为读者和我自己都开了个好头
  
  在日常编程中我经常需要标识存在于文本文档中部件和结构这些文档包括:日志文件、配置文件、定界数据以及格式更自由(但还是半结构化)报表格式所有这些文档都拥有它们自己“小语言”用于规定什么能够出现在文档内我编写这些非正式解析任务思路方法总是有点象大杂烩其中包括定制状态机、正则表达式以及上下文驱动串测试这些模式大概总是这样:“读些文本弄清是否可以用它来做些什么然后可能再多读些文本直尝试下去
  
  解析器将文档中部件和结构描述提炼成简明、清晰和介绍说明性规则确定由什么组成文档大多数正式解析器都使用扩展巴科斯范式(Extended Backus-Naur FormEBNF)上变体来描述它们所描述语言“语法”基本上EBNF 语法对您可能在文档中找到部件赋予名称;另外较大部件通常由较小部件组成小部件在较大部件中出现频率和顺序由操作符指定举例来说清单 1 是 EBNF 语法 typography.def我们在 SimpleParse 那篇文章中见到过这个语法(其它工具运行方式稍有区别):
  
  清单 1. typography.def
  para    := (plain / markup)+
  plain    := (word / whitespace / punctuation)+
  whitespace := [ tr]+
  alphanums  := [a-zA-Z0-9]+
  word    := alphanums, (wordpunct, alphanums)*, contraction?
  wordpunct  := [-_]
  contraction := "'", ('am'/'clock'/'d'/'ll'/'m'/'re'/'s'/'t'/'ve')
  markup   := emph / strong / module / code / title
  emph    := '-', plain, '-'
  strong   := '*', plain, '*'
  module   := '[', plain, ']'
  code    := "'", plain, "'"
  title    := '_', plain, '_'
  punctuation := (safepunct / mdash)
  mdash    := '--'
  safepunct  := [!@#$%^&|{}:;<>,.?/"]
  
  Spark 介绍
  Spark 解析器和 EBNF 语法有些共同的处但它将解析/处理过程分成了比传统 EBNF 语法所允许更小组件Spark 优点在于它对整个过程中每步操作控制都进行了微调还提供了将定制代码插入到过程中能力您如果读过本系列 SimpleParse 那篇文章您就会回想起我们过程是比较粗略:1)从语法(并从源文件)生成完整标记列表2)使用标记列表作为定制编程操作数据
  
  Spark 和标准基于 EBNF 工具相比缺点在于它比较冗长而且缺少直接出现计量符(即表示存在“+”表示可能性“*”和表示有限制性“?”)计量符可以在 Spark 记号赋予器(tokenizer)正则表达式中使用并可以用解析表达式语法中递归来进行模拟如果 Spark 允许在语法表达式中使用计量那就更好了个值得缺点是Spark 速度和 SimpleParse 使用基于 C 底层 mxTextTools 引擎相比逊色很多
  
  在“Compiling Little Languages in Python”(请参阅参考资料)中Spark 创始人 John Aycock 将编译器分成了 4个阶段本文讨论问题只涉及到前面两个半阶段这归咎于两方面原因是由于文章长度限制 2是我们将只讨论前篇文章提出同样相对来说比较简单“文本标记”问题Spark 还可以进步用作完整周期代码编译器/解释器而不是只用于我所描述“解析并处理”任务让我们来看看 Aycock 所说 4个阶段(引用时有所删节):
  
  扫描也称词法分析将输入流分成列记号
  解析也称语法分析确保记号列表在语法上是有效
  语义分析遍历抽象语法树(abstract syntax treeAST)次或多次收集信息并检查输入 makes sense
  生成代码再次遍历 AST这个阶段可能用 C 或汇编直接解释或输出代码
  对每个阶段Spark 都提供了个或多个抽象类以执行相应步骤还提供了个少见从而特化这些类Spark 具体类并不象大多数继承模式中类那样仅仅重新定义或添加特定思路方法而是具有两种特性(模式和各阶段和各种父模式都样)首先具体类所完成大部分工作都在思路方法文档串(doc)中指定第 2个特殊描述模式思路方法集将被赋予表明其角色独特名称父类反过来包含查找例子功能以进行操作内省(rospective)思路方法我们在参看举例时侯会更清楚地认识到这
  
  识别文本标记
  我已经用几种其它思路方法解决了这里问题我将种我称的为“智能 ASCII”格式用于各种目这种格式看起来很象为电子邮件和新闻组通信开发那些协定出于各种目我将这种格式自动地转换为其它格式如 HTML、XML 和 LaTeX我在这里还要再这样做为了让您直观地理解我意思我将在本文中使用下面这个简短样本:
  
  清单 2. 智能 ASCII 样本文本(p.txt)
  Text with *bold*, and -itals phrase-, and [module]--this
  should be a good 'practice run'.
  
  除了样本文件中内容还有另外点内容是有关格式但不是很多(尽管确有些细微的处是有关标记和标点如何交互)
  
  生成记号
  我们 Spark“智能 ASCII”解析器需要做件事就是将输入文本分成相关部件在记号赋予这我们还不想讨论如何构造记号让它们维持原样就可以了稍后我们会将记号序列组合成解析树
  
  上面 typography.def 中所示语法提供了 Spark 词法分析/扫描设计指南请注意我们只能使用那些在扫描阶段为“原语”名称也就是说那些包括其它已命名模式(复合)模式在解析阶段必须被延迟除了这样我们其实还可以直接复制旧语法
  
  清单 3. 删节后 wordscanner.py Spark 脚本
   WordScanner(GenericScanner):
    "Tokenize words, punctuation and markup"
    def tokenize(self, input):
      self.rv =
      GenericScanner.tokenize(self, input)
       self.rv
    def t_whitespace(self, s):
      r" [ tr]+ "
      self.rv.append(Token('whitespace', ' '))
    def t_alphanums(self, s):
      r" [a-zA-Z0-9]+ "
      pr "{word}",
      self.rv.append(Token('alphanums', s))
    def t_safepunct(self, s): ...
    def t_bracket(self, s): ...
    def t_asterisk(self, s): ...
    def t_underscore(self, s): ...
    def t_apostrophe(self, s): ...
    def t_dash(self, s): ...
  
   WordPlusScanner(WordScanner):
    "Enhance word/markup tokenization"
    def t_contraction(self, s):
      r" (?<=[a-zA-Z])'(am|clock|d|ll|m|re|s|t|ve) "
      self.rv.append(Token('contraction', s))
    def t_mdash(self, s):
      r' -- '
      self.rv.append(Token('mdash', s))
    def t_wordpunct(self, s): ...
    
  这里有个有趣地方WordScanner 本身是个完美扫描类;但 Spark 扫描类本身可以通过继承进步特化:子正则表达式模式在父正则表达式的前匹配而如果需要子思路方法/正则表达式可以覆盖父思路方法/正则表达式所以WordPlusScanner 将在 WordScanner 的前对特化进行匹配(可能会因此先获取些字节)模式文档串中允许使用任何正则表达式(举例来说.t_contraction 思路方法包含模式中个“向后插入”)
  
  不幸Python 2.2 在定程度上破坏了扫描继承逻辑在 Python 2.2 中不管在继承链中什么地方定义所有定义过模式都按字母顺序(按名称)进行匹配要修正这个问题您可以在 Spark _namelist 中修改行代码:
  
  清单 4. 纠正后相应 spark.py
  def _namelist(instance):
    namelist, namedict, list = , {}, [instance.____]
    for c in list:
      for b in c.__bases__:
        list.append(b)
      # for name in dir(c):  # dir behavior changed in 2.2
      for name in c.__dict__.keys: # <-- USE THIS
         not namedict.has_key(name):
          namelist.append(name)
          namedict[name] = 1
     namelist
    
  我已经向 Spark 创始人 John Aycock 通知了这个问题今后版本会修正这个问题同时请在您自己副本中作出修改
  
  让我们来看看
Tags:  python教程 python是什么 python平台 python

延伸阅读

最新评论

发表评论