Pythonic实现tokenizer的方法

时间:2009-03-27 19:20:44

标签: python coding-style tokenize

我将在Python中实现一个tokenizer,我想知道你是否可以提供一些样式建议?

我之前在C和Java中实现了一个标记化器,所以我对理论很好,我只想确保我遵循pythonic样式和最佳实践。

列出令牌类型:

例如,在Java中,我会有一个像这样的字段列表:

public static final int TOKEN_INTEGER = 0

但是,很明显,我认为没有办法(我认为)在Python中声明一个常量变量,所以我可以用普通的变量声明替换它,但这并没有让我成为一个很好的解决方案,因为声明可能会被改变。

从Tokenizer返回标记:

还有一个更好的选择,只需返回一个元组列表,例如

[ (TOKEN_INTEGER, 17), (TOKEN_STRING, "Sixteen")]?

干杯,

皮特

12 个答案:

答案 0 :(得分:56)

re模块中有一个名为re.Scanner的未记录的类。使用标记化器非常简单:

import re
scanner=re.Scanner([
  (r"[0-9]+",       lambda scanner,token:("INTEGER", token)),
  (r"[a-z_]+",      lambda scanner,token:("IDENTIFIER", token)),
  (r"[,.]+",        lambda scanner,token:("PUNCTUATION", token)),
  (r"\s+", None), # None == skip token.
])

results, remainder=scanner.scan("45 pigeons, 23 cows, 11 spiders.")
print results

将导致

[('INTEGER', '45'),
 ('IDENTIFIER', 'pigeons'),
 ('PUNCTUATION', ','),
 ('INTEGER', '23'),
 ('IDENTIFIER', 'cows'),
 ('PUNCTUATION', ','),
 ('INTEGER', '11'),
 ('IDENTIFIER', 'spiders'),
 ('PUNCTUATION', '.')]

我用re.Scanner编写了一个非常漂亮的配置/结构化数据格式解析器,只用了几百行。

答案 1 :(得分:29)

Python采用“我们都同意成人”的信息隐藏方法。可以使用变量就像它们是常量一样,并且相信代码的用户不会做一些愚蠢的事情。

答案 2 :(得分:10)

在很多情况下,exp。在解析长输入流时,您可能会发现将tokenizer实现为生成器函数更有用。这样,您可以轻松遍历所有令牌,而无需大量内存来构建令牌列表。

对于生成器,请参阅original proposal或其他在线文档

答案 3 :(得分:7)

感谢您的帮助,我已经开始将这些想法融合在一起了,我想出了以下内容。这个实现有什么特别的错误(特别是我担心将文件对象传递给tokenizer):

class Tokenizer(object):

  def __init__(self,file):
     self.file = file

  def __get_next_character(self):
      return self.file.read(1)

  def __peek_next_character(self):
      character = self.file.read(1)
      self.file.seek(self.file.tell()-1,0)
      return character

  def __read_number(self):
      value = ""
      while self.__peek_next_character().isdigit():
          value += self.__get_next_character()
      return value

  def next_token(self):
      character = self.__peek_next_character()

      if character.isdigit():
          return self.__read_number()

答案 4 :(得分:4)

“只是简单地返回一个元组列表有更好的选择吗?”

不。它的效果非常好。

答案 5 :(得分:2)

“只是简单地返回一个元组列表有更好的选择吗?”

这就是“tokenize”模块用于解析Python源代码的方法。返回一个简单的元组列表可以很好地工作。

答案 6 :(得分:2)

我最近也建立了一个标记器,并通过了一些问题。

令牌类型在模块级别被声明为“常量”,即具有ALL_CAPS名称的变量。例如,

_INTEGER = 0x0007
_FLOAT = 0x0008
_VARIABLE = 0x0009

等等。我在名称前面使用了下划线来指出那些字段对于模块来说是“私有的”,但我真的不知道这是典型的还是可取的,甚至不是Pythonic的多少。 (另外,我可能会抛弃数字而不支持字符串,因为在调试过程中它们更具可读性。)

标记作为命名元组返回。

from collections import namedtuple
Token = namedtuple('Token', ['value', 'type'])
# so that e.g. somewhere in a function/method I can write...
t = Token(n, _INTEGER)
# ...and return it properly

我使用了命名元组,因为使用名称(例如token.value)而不是索引(例如token [0])时,tokenizer的客户端代码(例如解析器)似乎更清晰一些。

最后,我注意到有时候,特别是编写测试,我更喜欢将字符串传递给tokenizer而不是文件对象。我把它称为“读者”,并有一个特定的方法来打开它,让令牌器通过相同的接口访问它。

def open_reader(self, source):
    """
    Produces a file object from source.
    The source can be either a file object already, or a string.
    """
    if hasattr(source, 'read'):
        return source
    else:
        from io import StringIO
        return StringIO(source)

答案 7 :(得分:1)

当我在Python中开始新的东西时,我通常首先看一些模块或库来使用。已经有90%的可能性已经存在。

对于标记器和解析器,这当然是这样。你看过PyParsing吗?

答案 8 :(得分:1)

我已经为类C编程语言实现了一个tokenizer。我所做的是将令牌的创建分为两层:

  • 一个表面扫描器:这个实际读取文本并使用正则表达式将其拆分为最原始的标记(运算符,标识符,数字......);这个产生元组(tokenname,scansstring,startpos,endpos)。
  • 一个标记生成器:这会消耗第一层的元组,将它们变成标记对象(我认为命名元组也会这样做)。它的目的是检测令牌流中的一些长程依赖,特别是字符串(带有开始和结束引号)和注释(打开一个结束的词汇; - 是的,我想保留评论!)并将它们强制为单个令牌。然后,生成的令牌对象流将返回到正在使用的解析器。

两者都是发电机。这种方法的好处是:

  • 原始文本的阅读只能以最原始的方式进行,使用简单的正则表达式 - 快速而干净。
  • 第二层已经实现为原始解析器,用于检测字符串文字和注释 - 重用解析器技术。
  • 您不必使用复杂的检测方法对表面扫描仪施加压力。
  • 但真正的解析器在要解析的语言的语义级别上获得令牌(再次是字符串,注释)。

我对这种分层方法感到非常满意。

答案 9 :(得分:1)

我转向David Mertz的优秀 Text Processing in Python

答案 10 :(得分:1)

这是一个迟到的答案,现在官方文档中有一些内容:Writing a tokenizer带有re标准库。这是Python 3文档中的内容,不在Py 2.7文档中。但它仍适用于较老的蟒蛇。

这包括短代码,简易设置和编写生成器,这里提出了几个答案。

如果文档不是Pythonic,我不知道是什么: - )

答案 11 :(得分:0)

“只是简单地返回一个元组列表是否有更好的选择”

我必须实现一个tokenizer,但它需要一个比元组列表更复杂的方法,因此我为每个标记实现了一个类。然后,您可以返回类实例列表,或者如果要保存资源,可以返回实现迭代器接口的内容,并在解析过程中生成下一个标记。