Подтвердить что ты не робот

Питонический способ реализации токенизатора

Я собираюсь реализовать токенизатор в Python, и мне было интересно, можете ли вы предложить какой-нибудь совет стиля?

Я реализовал токенизатор раньше в C и на Java, поэтому я в порядке с теорией, я бы просто хотел, чтобы я следил за питоническими стилями и лучшими практиками.

Типы токенов листинга:

В Java, например, у меня будет список таких полей:

public static final int TOKEN_INTEGER = 0

Но, очевидно, нет способа (я думаю) объявить постоянную переменную в Python, поэтому я мог бы просто заменить это на обычные объявления переменных, но это не делает меня отличным решением, поскольку объявления могут быть изменены.

Возврат токенов из Tokenizer:

Есть ли лучшая альтернатива простому возврату списка кортежей, например.

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

Приветствия,

Пит

4b9b3361

Ответ 1

Python использует подход "все соглашающиеся взрослые" к скрытию информации. Хорошо использовать переменные, как если бы они были константами, и полагать, что пользователи вашего кода не будут делать что-то глупое.

Ответ 2

В модуле 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 для написания довольно элегантного анализатора формата/структурированного формата данных всего за пару сотен строк.

Ответ 3

Во многих ситуациях exp. при синтаксическом анализе длинных входных потоков вы можете счесть более полезным реализовать токенизатор как функцию генератора. Таким образом, вы можете легко перебрать все токены, не требуя большого объема памяти, чтобы сначала создать список токенов.

Для генератора см. исходное предложение или другие онлайн-документы

Ответ 4

Спасибо за вашу помощь, я начал объединять эти идеи, и я придумал следующее. Есть ли что-то ужасное в этой реализации (особенно я заинтересован в передаче объекта файла в токенизатор):

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()

Ответ 5

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

Неа. Он работает очень хорошо.

Ответ 6

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

Это подход, используемый модулем "tokenize" для синтаксического анализа исходного кода Python. Возвращение простого списка кортежей может работать очень хорошо.

Ответ 7

Недавно я создал токенизатор и передал некоторые из ваших проблем.

Типы токенов объявляются как "константы", т.е. переменные с именами ALL_CAPS, на уровне модуля. Например,

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

и т.д. Я использовал подчеркивание перед именем, чтобы указать, что каким-то образом эти поля "private" для модуля, но я действительно не знаю, является ли это типичным или целесообразным, даже не сколько 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) вместо индексов (например, токена [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)

Ответ 8

В последнее время в официальной документации есть что-то: Написание токенизатора с помощью стандартной библиотеки re. Это содержание в документации Python 3, которая отсутствует в документах Py 2.7. Но он по-прежнему применим к старшим питонам.

Это включает в себя как короткий код, легкую настройку, так и запись генератора, поскольку здесь предлагаются несколько ответов.

Если документы не являются Pythonic, я не знаю, что есть: -)

Ответ 9

Когда я запускаю что-то новое в Python, я обычно смотрю сначала на некоторые модули или библиотеки для использования. Там 90% + вероятность того, что уже есть что-то доступное.

Для токенизаторов и парсеров это, безусловно, так. Вы посмотрели на PyParsing?

Ответ 10

Я реализовал токенизатор для C-подобного языка программирования. Я сделал, чтобы разделить создание токенов на два слоя:

  • поверхностный сканер: на самом деле он читает текст и использует регулярное выражение, чтобы разделить его на только самые примитивные маркеры (операторы, идентификаторы, числа,...); это дает кортежи (tokenname, scannedstring, startpos, endpos).
  • токенизатор: это потребляет кортежи из первого слоя, превращая их в объекты токена (по-моему, так называемые кортежи тоже будут работать). Его цель - обнаружить некоторые долгосрочные зависимости в потоке токенов, в частности строки (с их открывающими и закрывающимися кавычками) и комментарии (с их открытием закрывающих лексем; - да, я хотел сохранить комментарии!) И принудить их к одиночным жетоны. Затем результирующий поток объектов маркера возвращается к потребляющему парсеру.

Оба являются генераторами. Преимуществами этого подхода были:

  • Чтение необработанного текста выполняется только самым примитивным способом, с простым регулярным выражением - быстрым и чистым.
  • Второй уровень уже реализован как примитивный парсер, чтобы определить строковые литералы и комментарии - повторное использование технологии парсера.
  • Вам не нужно напрягать поверхностный сканер с комплексными обнаружениями.
  • Но реальный синтаксический анализатор получает токены на семантическом уровне языка для анализа (снова строки, комментарии).

Я очень доволен этим многоуровневым подходом.

Ответ 12

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

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