我将在Python中实现一个tokenizer,我想知道你是否可以提供一些样式建议?
我之前在C和Java中实现了一个标记化器,所以我对理论很好,我只想确保我遵循pythonic样式和最佳实践。
列出令牌类型:
例如,在Java中,我会有一个像这样的字段列表:
public static final int TOKEN_INTEGER = 0
但是,很明显,我认为没有办法(我认为)在Python中声明一个常量变量,所以我可以用普通的变量声明替换它,但这并没有让我成为一个很好的解决方案,因为声明可能会被改变。
从Tokenizer返回标记:
还有一个更好的选择,只需返回一个元组列表,例如
[ (TOKEN_INTEGER, 17), (TOKEN_STRING, "Sixteen")]?
干杯,
皮特
答案 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。我所做的是将令牌的创建分为两层:
两者都是发电机。这种方法的好处是:
我对这种分层方法感到非常满意。
答案 9 :(得分:1)
我转向David Mertz的优秀 Text Processing in Python
答案 10 :(得分:1)
这是一个迟到的答案,现在官方文档中有一些内容:Writing a tokenizer带有re
标准库。这是Python 3文档中的内容,不在Py 2.7文档中。但它仍适用于较老的蟒蛇。
这包括短代码,简易设置和编写生成器,这里提出了几个答案。
如果文档不是Pythonic,我不知道是什么: - )
答案 11 :(得分:0)
“只是简单地返回一个元组列表是否有更好的选择”
我必须实现一个tokenizer,但它需要一个比元组列表更复杂的方法,因此我为每个标记实现了一个类。然后,您可以返回类实例列表,或者如果要保存资源,可以返回实现迭代器接口的内容,并在解析过程中生成下一个标记。