tokenize —用于 Python 的 Tokenizer 源

源代码: Lib/tokenize.py


tokenize模块为 Python 源代码提供了词法扫描器,该词法扫描器以 Python 实现。该模块中的扫描仪也以令牌的形式返回 Comments,这对于实现“漂亮打印机”(包括用于屏幕显示的着色器)非常有用。

为了简化令牌流的处理,使用通用的OP令牌类型返回所有operatordelimiter令牌以及Ellipsis。可以pass检查tokenize.tokenize()返回的named tupleexact_type属性来确定确切的类型。

Tokenizing Input

主要入口点是generator

  • tokenize. tokenize(* readline *)
    • tokenize()生成器需要一个参数* readline *,该参数必须是可调用的对象,该对象提供与文件对象的io.IOBase.readline()方法相同的接口。每次对函数的调用都应以字节为单位返回一行 Importing。

生成器生成具有以下成员的 5Tuples:令牌类型;令牌字符串; 2 元整数(srow, scol) int,用于指定源中令牌开始的行和列;一个 2 整数(erow, ecol)的整数,指定标记在源中结束的行和列;以及找到令牌的行。传递的行(最后一个 Tuples 项)是* physical *行。 5 个 Tuples 以named tuple的形式返回,其字段名称为type string start end line

返回的named tuple具有名为exact_type的附加属性,其中包含OP令牌的确切运算符类型。对于所有其他令牌类型,exact_type等于命名的 Tuplestype字段。

在版本 3.1 中进行了更改:添加了对命名 Tuples 的支持。

在版本 3.3 中进行了更改:添加了对exact_type的支持。

tokenize()根据 PEP 263pass查找 UTF-8 BOM 或编码 cookie 来确定文件的源编码。

  • tokenize. generate_tokens(* readline *)
    • 对读取 unicode 字符串而不是字节的源标记化。

tokenize()一样,* readline 参数是可调用的,返回单行 Importing。但是,generate_tokens()期望 readline *返回一个 str 对象而不是字节。

结果是一个迭代器产生了名为 tuple 的 Tuples,就像tokenize()一样。它不会产生ENCODING令牌。

token模块中的所有常量也都从tokenize中导出。

提供了另一个Function来逆转令牌化过程。这对于创建标记脚本脚本,修改标记流以及写回修改后脚本的工具很有用。

  • tokenize. untokenize(可迭代)
    • 将令牌转换回 Python 源代码。 * iterable *必须返回至少包含两个元素的序列:令牌类型和令牌字符串。任何其他序列元素都将被忽略。

重建的脚本作为单个字符串返回。保证结果可以令牌化回去以匹配 Importing,从而使转换无损并确保往返。由于令牌之间的间距(列位置)可能会更改,因此该保证仅适用于令牌类型和令牌字符串。

它返回使用ENCODING令牌编码的字节,这是tokenize()输出的第一个令牌序列。如果 Importing 中没有编码令牌,它将返回一个 str。

tokenize()需要检测它标记化的源文件的编码。它用于执行此操作的Function可用:

  • tokenize. detect_encoding(* readline *)
    • detect_encoding()函数用于检测解码 Python 源文件时应使用的编码。与tokenize()生成器相同,它需要一个参数 readline。

它将最多调用 readline 两次,并返回已使用的编码(作为字符串)和所有已读入的行的列表(未从字节解码)。

它根据 PEP 263中指定的 UTF-8 BOM 或编码 cookie 来检测编码。如果同时存在 BOM 和 cookie,但不同意,则会引发SyntaxError。请注意,如果找到 BOM,则将返回'utf-8-sig'作为编码。

如果未指定编码,则将返回默认值'utf-8'

使用open()打开 Python 源文件:使用detect_encoding()检测文件编码。

  • tokenize. open(* filename *)

3.2 版中的新Function。

  • exception tokenize. TokenError
    • 当文档字符串或表达式可能会分成几行时,在文件中的任何位置未完成时引发,例如:
"""Beginning of
docstring

or:

[1,
 2,
 3

请注意,未封闭的单引号字符串不会引起错误。它们被标记为ERRORTOKEN,随后是其内容的标记。

Command-Line Usage

版本 3.3 中的新Function。

tokenize模块可以从命令行作为脚本执行。它很简单:

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

接受以下选项:

  • -h `,` `--help`

    • 显示此帮助消息并退出
  • -e `,` `--exact`

    • 使用确切类型显示令牌名称

如果指定了filename.py,其内容将标记为 stdout。否则,将在标准 Importing 上执行标记化。

Examples

将浮点 Literals 转换为 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()读取 unicode 字符串而不是字节:

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)