tokenize — Токенизатор для исходного кода Python


Модуль tokenize предоставляет лексический сканер исходного кода Python, реализованный на Python. Сканер также возвращает комментарии в виде токенов, что делает его полезным для реализации «красивых принтеров», включая раскраски для экранных дисплеев.

Чтобы упростить обработку потока маркеров, все маркеры операторов и разделителей, а также Ellipsis возвращаются с использованием общего типа маркера OP. Точный тип можно определить, проверив свойство exact_type в именованном кортеже, возвращённом tokenize.tokenize().

Токенизация ввода

Основной точкой входа является генератор:

tokenize.tokenize(readline)

Генератору tokenize() требуется один аргумент readline, который должен быть вызываемым объектом, предоставляющим тот же интерфейс, что и метод io.IOBase.readline() файловых объектов. Каждый вызов функции должен возвращать одну строку ввода в виде байтов.

Генератор создаёт 5 кортежей со следующими элементами: тип токена; строка токена; 2-кортеж (srow, scol) целых чисел, указывающий строку и столбец, где токен начинается в источнике; 2-кортеж (erow, ecol) целых чисел, указывающий строку и столбец, где токен заканчивается в источнике; и строку, на которой был найден токен. Пройденная строка (последний элемент кортежа) — это физическая строка. 5 кортеж возвращается как именованный кортеж с именами полей: type string start end line.

У возвращённого именованного кортежа есть дополнительное свойство с именем exact_type, которое содержит точный тип оператора для токенов OP. Для всех других типов токенов exact_type соответствует полю type именованного кортежа.

Изменено в версии 3.1: Добавлена поддержка именованных кортежей.

Изменено в версии 3.3: Добавлена поддержка exact_type.

tokenize() определяет исходную кодировку файла, ища спецификацию UTF-8 или cookie кодировки в соответствии с PEP 263.

tokenize.generate_tokens(readline)

Токенизация исходного кода из Юникод строки вместо байтов.

Как и tokenize(), аргумент readline является вызываемым, возвращающим одну строку ввода. Однако generate_tokens() ожидает, что readline вернёт строковый объект, а не байты.

В результате итератор выдает именованные кортежи точно так же, как tokenize(). Он не отдаёт ENCODING токен.

Все константы из модуля token также экспортируются из tokenize.

Предусмотрена ещё одна функция для обратного процесса токенизации. Это полезно для создания инструментов, которые токенизируют сценарий, изменяют поток маркеров и записывают изменённый скрипт.

tokenize.untokenize(iterable)

Преобразует токены обратно в исходный код Python. iterable должен возвращать последовательности, содержащие как минимум два элемента: тип токена и строку токена. Любые дополнительные элементы последовательности игнорируются.

Восстановленный скрипт возвращается в виде одной строки. Результат гарантированно токенизируется обратно, чтобы соответствовать вводу, так что преобразование происходит без потерь и гарантируется двустороннее обращение. Гарантия распространяется только на тип токена и строку токена, т. к. расстояние между токенами (позиции столбцов) может измениться.

Возвращает байты, закодированные с использованием токена ENCODING, который является первой последовательностью токенов, выводимой tokenize(). Если во входных данных нет токена кодировки, вместо этого возвращается str.

tokenize() должна определять кодировку исходных файлов, которые она токенизирует. Функция, которую она использует для этого, доступна:

tokenize.detect_encoding(readline)

Функция detect_encoding() используется для определения кодировки, которую следует использовать для декодирования исходного файла Python. Она требует readline аргумента, так же, как генератор tokenize().

Она вызовет readline не более двух раз и вернёт использованную кодировку (в виде строки) и список любых строк (не декодированных из байтов), которые она прочитал.

Она определяет кодировку по наличию спецификации UTF-8 или cookie файла кодировки, как указано в PEP 263. Если и спецификация, и cookie присутствуют, но не совпадают, будет вызвано SyntaxError. Обратите внимание, что если спецификация найдена, будет возвращена 'utf-8-sig' как кодировка.

Если кодировка не указана, будет возвращено значение по умолчанию 'utf-8'.

Используйте open() для открытия исходных файлов Python: она использует detect_encoding() для определения кодировки файла.

tokenize.open(filename)

Открывает файл в режиме только для чтения, используя кодировку, обнаруженную detect_encoding().

Добавлено в версии 3.2.

exception tokenize.TokenError

Вызывается, когда строка документа или выражение, которое может быть разделено на несколько строк, нигде в файле не завершены. Например

"""Начало
строки документации

или же:

[1,
 2,
 3

Обратите внимание, что незакрытые строки в одинарных кавычках не вызывают возникновения ошибки. Они токенизированы как ERRORTOKEN с последующей токенизацией их содержимого.

Использование командной строки

Добавлено в версии 3.3.

Модуль tokenize можно запустить как скрипт из командной строки. Это так же просто, как:

python -m tokenize [-e] [filename.py]

Принимаются следующие опции:

-h, --help

показать справочное сообщение и выйти

-e, --exact

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

Если указан filename.py, его содержимое токенизируется в стандартный вывод. В противном случае токенизация выполняется на стандартном вводе.

Примеры

Пример сценария переписчика, который преобразует литералы с плавающей запятой в объекты Decimal:

from tokenize import tokenize, untokenize, NUMBER, STRING, NAME, OP
from io import BytesIO

def decistmt(s):
    """Заменяем Decimal на числа с плавающей запятой в строке операторов.

    >>> from decimal import Decimal
    >>> s = 'print(+21.3e-5*-.1234/81.7)'
    >>> decistmt(s)
    "print (+Decimal ('21.3e-5')*-Decimal ('.1234')/Decimal ('81.7'))"

    Формат экспоненты унаследован от библиотеки платформы C. Известны
    случаи «e-007» (Windows) и «e-07» (не Windows). Поскольку мы показываем
    только 12 цифр, а 13-я не близка к 5, остальная часть вывода должна
    быть независимой от платформы.

    >>> exec(s)  #doctest: +ELLIPSIS
    -3.21716034272e-0...7

    Результат вычислений с Decimal должен быть одинаковым на всех
    платформах.

    >>> exec(decistmt(s))
    -3.217160342717258261933904529E-7
    """
    result = []
    g = tokenize(BytesIO(s.encode('utf-8')).readline)  # токенизируем строку
    for toknum, tokval, _, _, _ in g:
        if toknum == NUMBER and '.' in tokval:  # заменяем NUMBER токены
            result.extend([
                (NAME, 'Decimal'),
                (OP, '('),
                (STRING, repr(tokval)),
                (OP, ')')
            ])
        else:
            result.append((toknum, tokval))
    return untokenize(result).decode('utf-8')

Пример токенизации из командной строки. Сценарий:

def say_hello():
    print("Hello, World!")

say_hello()

будет токенизирован для следующего вывода, где первый столбец — это диапазон координат строки/столбца, где находится токен, второй столбец — это имя токена, а последний столбец — это значение токена (если есть)

$ python -m tokenize hello.py
0,0-0,0:            ENCODING       'utf-8'
1,0-1,3:            NAME           'def'
1,4-1,13:           NAME           'say_hello'
1,13-1,14:          OP             '('
1,14-1,15:          OP             ')'
1,15-1,16:          OP             ':'
1,16-1,17:          NEWLINE        '\n'
2,0-2,4:            INDENT         '    '
2,4-2,9:            NAME           'print'
2,9-2,10:           OP             '('
2,10-2,25:          STRING         '"Hello, World!"'
2,25-2,26:          OP             ')'
2,26-2,27:          NEWLINE        '\n'
3,0-3,1:            NL             '\n'
4,0-4,0:            DEDENT         ''
4,0-4,9:            NAME           'say_hello'
4,9-4,10:           OP             '('
4,10-4,11:          OP             ')'
4,11-4,12:          NEWLINE        '\n'
5,0-5,0:            ENDMARKER      ''

Точные имена типов токенов можно отобразить с помощью параметра -e:

$ python -m tokenize -e hello.py
0,0-0,0:            ENCODING       'utf-8'
1,0-1,3:            NAME           'def'
1,4-1,13:           NAME           'say_hello'
1,13-1,14:          LPAR           '('
1,14-1,15:          RPAR           ')'
1,15-1,16:          COLON          ':'
1,16-1,17:          NEWLINE        '\n'
2,0-2,4:            INDENT         '    '
2,4-2,9:            NAME           'print'
2,9-2,10:           LPAR           '('
2,10-2,25:          STRING         '"Hello, World!"'
2,25-2,26:          RPAR           ')'
2,26-2,27:          NEWLINE        '\n'
3,0-3,1:            NL             '\n'
4,0-4,0:            DEDENT         ''
4,0-4,9:            NAME           'say_hello'
4,9-4,10:           LPAR           '('
4,10-4,11:          RPAR           ')'
4,11-4,12:          NEWLINE        '\n'
5,0-5,0:            ENDMARKER      ''

Пример программной разметки файла, чтения строк Юникода вместо байтов с помощью generate_tokens():

import tokenize

with tokenize.open('hello.py') as f:
    tokens = tokenize.generate_tokens(f.readline)
    for token in tokens:
        print(token)

Или чтение байтов напрямую с помощью tokenize():

import tokenize

with open('hello.py', 'rb') as f:
    tokens = tokenize.tokenize(f.readline)
    for token in tokens:
        print(token)