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

Источник: Lib/tokenize.py


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

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

Предупреждение

Обратите внимание, что функции в этом модуле предназначены только для разбора синтаксически корректного кода Python (кода, который не поднимается при разборе с использованием ast.parse()). Поведение функций в этом модуле неопределено при предоставлении некорректного кода Python и может измениться в любой момент.

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

Первичной точкой входа является 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(). Если во входных данных нет маркера кодировки, вместо него возвращается str.

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

Added in version 3.2.

exception tokenize.TokenError

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

"""Beginning of
docstring

или:

[1,
 2,
 3

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

Added in version 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)