12

我将在 Python 中实现一个标记器,我想知道你是否可以提供一些风格建议?

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

列出令牌类型:

例如,在 Java 中,我会有一个字段列表,如下所示:

public static final int TOKEN_INTEGER = 0

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

从 Tokenizer 返回令牌:

有没有更好的选择来简单地返回一个元组列表,例如

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

干杯,

皮特

4

12 回答 12

62

模块中有一个未记录的类,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 编写了一个非常漂亮的配置/结构化数据格式解析器,只用了几百行。

于 2009-03-29T00:01:04.957 回答
29

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

于 2009-03-27T19:24:07.987 回答
11

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

对于生成器,请参阅原始提案或其他在线文档

于 2009-03-27T19:45:28.387 回答
7

感谢您的帮助,我已经开始将这些想法整合在一起,并提出以下建议。这个实现有什么严重的错误(特别是我担心将文件对象传递给标记器):

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()
于 2009-03-27T21:48:18.170 回答
4

“除了简单地返回一个元组列表之外,还有更好的选择吗?”

没有。它真的很好用。

于 2009-03-27T19:24:29.437 回答
3

“除了简单地返回一个元组列表之外,还有更好的选择吗?”

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

于 2009-03-27T19:30:49.200 回答
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])时看起来更清晰一些。

最后,我注意到有时,尤其是在编写测​​试时,我更喜欢将字符串传递给标记器而不是文件对象。我称它为“阅读器”,并有一个特定的方法来打开它,让分词器通过相同的接口访问它。

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)
于 2009-03-28T01:20:48.703 回答
1

当我在 Python 中开始一些新的东西时,我通常会首先查看一些要使用的模块或库。有 90% 以上的机会已经有可用的东西。

对于分词器和解析器来说,确实如此。你看过PyParsing吗?

于 2009-03-28T08:16:06.430 回答
1

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

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

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

  • 原始文本的阅读仅以最原始的方式完成,使用简单的正则表达式 - 快速而干净。
  • 第二层已经实现为原始解析器,用于检测字符串文字和注释 - 重用解析器技术。
  • 您不必使用复杂的检测来拉紧表面扫描仪。
  • 但是真正的解析器在要解析的语言的语义级别上获取标记(同样是字符串,注释)。

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

于 2009-07-24T10:28:01.187 回答
1

我会求助于David Mertz的 Python 中出色的文本处理

于 2009-07-24T10:36:27.863 回答
1

这是一个较晚的答案,现在官方文档中有一些内容:使用标准库编写标记器。re这是 Python 3 文档中的内容,而不是 Py 2.7 文档中的内容。但它仍然适用于较旧的 Python。

这包括短代码、简单设置和编写生成器,正如这里提出的几个答案。

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

于 2013-04-22T08:57:53.867 回答
0

“有没有比简单地返回元组列表更好的选择”

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

于 2009-03-27T19:56:48.110 回答