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

Исходный код: Lib/tokenize.py.


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

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

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

Первичной точкой входа является generator:

tokenize.tokenize(readline)

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

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

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

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

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

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

tokenize.generate_tokens(readline)

Токенизировать источник, читающий строки юникода вместо байтов.

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

Результатом является итератор, выдающий именованные кортежи, точно так же, как tokenize(). Он не дает токена ENCODING.

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

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

tokenize.untokenize(iterable)

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

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

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

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

tokenize.detect_encoding(readline)

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

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

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

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

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

tokenize.open(filename)

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

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

exception tokenize.TokenError

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

"""Beginning of
docstring

или:

[1,
 2,
 3

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

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

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

Модуль tokenize может быть выполнен как сценарий из командной строки. Это очень просто:

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

Принимаются следующие варианты:

-h, --help

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

-e, --exact

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

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

Примеры

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

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

def decistmt(s):
    """Substitute Decimals for floats in a string of statements.

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

    The format of the exponent is inherited from the platform C library.
    Known cases are "e-007" (Windows) and "e-07" (not Windows).  Since
    we're only showing 12 digits, and the 13th isn't close to 5, the
    rest of the output should be platform-independent.

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

    Output from calculations with Decimal should be identical across all
    platforms.

    >>> exec(decistmt(s))
    -3.217160342717258261933904529E-7
    """
    result = []
    g = tokenize(BytesIO(s.encode('utf-8')).readline)  # tokenize the string
    for toknum, tokval, _, _, _ in g:
        if toknum == NUMBER and '.' in tokval:  # replace NUMBER tokens
            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)
Вернуться на верх