Подходящие для Python способ реализации токенизатор
Я собираюсь реализовать токенизатор в Python, и мне было интересно, можете ли вы предложить некоторые советы по стилю?
я реализовал токенизатор раньше в C и на Java, поэтому я в порядке с теорией, я просто хотел бы убедиться, что я следую питоническим стилям и лучшим практикам.
Список Типов Токенов:
в Java, например, у меня был бы список таких полей:
public static final int TOKEN_INTEGER = 0
но, очевидно, нет способа (я думаю) объявить константу переменная в Python, поэтому я мог бы просто заменить это обычными объявлениями переменных, но это не кажется мне отличным решением, так как объявления могут быть изменены.
Возврат Токенов Из Токенизатора:
есть ли лучшая альтернатива просто возвращению списка кортежей, например
[ (TOKEN_INTEGER, 17), (TOKEN_STRING, "Sixteen")]?
спасибо,
Пит
12 ответов
Python использует подход" мы все взрослые " к сокрытию информации. Можно использовать переменные как константы и верить, что пользователи вашего кода не сделают ничего глупого.
есть недокументированный класс в 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.Сканер для написания довольно изящной конфигурации / структурированного парсера формата данных всего в пару сотен строк.
во многих ситуациях, exp. при анализе длинных входных потоков может оказаться более полезным реализовать tokenizer в качестве функции генератора. Таким образом, вы можете легко перебирать все токены без необходимости большого объема памяти для создания списка токенов.
для генератора см. оригинальное предложение или другие онлайн-документы
Спасибо за вашу помощь, я начал приводить эти идеи вместе, и я придумал следующее. Есть ли что-то ужасно неправильное в этой реализации (особенно меня беспокоит передача объекта файла в 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()
" есть ли лучшая альтернатива просто возвращению списка кортежей?"
Это подход, используемый модулем "tokenize" для разбора исходного кода Python. Возвращает список кортежей может работать очень хорошо.
недавно я также построил токенизатор и прошел через некоторые из ваших проблем.
типы токенов объявляются как "константы", т. е. переменные с именами ALL_CAPS, на уровне модуля. Например,
_INTEGER = 0x0007
_FLOAT = 0x0008
_VARIABLE = 0x0009
и так далее. Я использовал знак подчеркивания перед именем указать на то, что как-то эти поля являются "частными" для модуля, но я действительно не знаю, характерно ли это или желательно, даже не сколько для Python. (Кроме того, я, вероятно, буду бросать номера в предпочтение строк, потому что во время отладки они гораздо более читабельны.)
токены возвращаются как именованные кортежи.
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
я использовал именованные кортежи, потому что клиентский код токенизатора (например, парсер) кажется немного более ясным при использовании имен (например, токен.value) вместо индексов (например, token[0]).
наконец, я заметил, что иногда, особенно при написании тестов, я предпочитаю передавать строку в tokenizer вместо объекта file. Я называю это "читатель", и имеют определенный метод, чтобы открыть его и позволить 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)
когда я начинаю что-то новое в Python, я обычно сначала смотрю на некоторые модули или библиотеки для использования. Есть 90%+ шанс, что уже есть что-то доступное.
для токенизаторов и парсеров это, безусловно, так. Вы смотрели на когда pyparsing ?
я реализовал токенизатор для C-подобного языка программирования. Что я сделал, так это разделил создание токенов на два слоя:
- a поверхность сканера: этот фактически читает текст и использует регулярное выражение, чтобы разделить его только на самые примитивные токены (операторы, идентификаторы, числа,...); это дает кортежи (tokenname, scannedstring, startpos, endpos).
- a tokenizer: это уничтожает кортежи от первый слой, превращая их в объекты токенов (я думаю, что именованные кортежи также подойдут). Его цель-обнаружить некоторые долгосрочные зависимости в потоке токенов, в частности строки (с их открывающими и закрывающими кавычками) и комментарии (с их открывающими закрывающими лексемами; - да, я хотел сохранить комментарии!) и принуждать их к единым жетонам. Затем результирующий поток объектов токенов возвращается потребляющему синтаксическому анализатору.
обоих генераторов. Преимущества этого подходили:
- чтение необработанного текста выполняется только самым примитивным способом, с помощью простых регулярных выражений-быстро и чисто.
- второй слой уже реализован как примитивный парсер, для выявления строковых литералов и комментариев - повторное использование парсера технологии.
- вам не нужно напрягать сканер поверхности со сложными обнаружениями.
- но реальный парсер получает токены на семантическом уровне языка для анализа (снова строки, Примечание.)
Я чувствую себя вполне счастливым с этим многоуровневым подходом.
Я бы обратился к превосходному обработка текста в Python Дэвид Мерц
это поздний ответ, теперь есть что-то в официальной документации: написание токенизатор С re
стандартная библиотека. Это содержимое в документации Python 3, которого нет в документах py 2.7. Но он по-прежнему применим к старым питонам.
Это включает в себя как короткий код, легкость установки и написание генератора как несколько ответов здесь предложили.
Если документы не являются Pythonic, я не знаю, что такое: -)
"есть ли лучшая альтернатива просто возвращению списка кортежей"
Мне пришлось реализовать токенизатор, но он требовал более сложного подхода, чем список кортежей, поэтому я реализовал класс для каждого токена. Затем вы можете вернуть список экземпляров класса или, если вы хотите сохранить ресурсы, вы можете вернуть что-то, реализующее интерфейс итератора, и создать следующий токен во время выполнения синтаксического анализа.