Подходящие для 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, я не знаю, что такое: -)


"есть ли лучшая альтернатива просто возвращению списка кортежей"

Мне пришлось реализовать токенизатор, но он требовал более сложного подхода, чем список кортежей, поэтому я реализовал класс для каждого токена. Затем вы можете вернуть список экземпляров класса или, если вы хотите сохранить ресурсы, вы можете вернуть что-то, реализующее интерфейс итератора, и создать следующий токен во время выполнения синтаксического анализа.