Skip to main content

32.7. tokenize — Python源代码

源代码: Lib/tokenize.py


tokenize 模块为Python源代码提供了一个词法扫描器,在Python中实现。此模块中的扫描器也会返回注释作为令牌,使其对于实现“漂亮打印机”(包括屏幕显示的着色器)很有用。

为了简化令牌流处理,使用通用 token.OP 令牌类型返回所有 操作员分隔符 令牌。确切的类型可以通过检查从 tokenize.tokenize() 返回的 named tuple 上的 exact_type 属性来确定。

32.7.1. 令牌化输入

主要入口点是 generator

tokenize.tokenize(readline)

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

生成器产生具有这些成员的5元组:令牌类型;令牌字符串;指定令牌在源中开始的行和列的int的2元组 (srow, scol);一个2元组 (erow, ecol),指定令牌在源中结束的行和列;以及找到令牌的行。通过的行(最后一个元组项)是 logical 行;连续线。 5元组作为 named tuple 返回,字段名称为:type string start end line

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

在 3.1 版更改: 添加了对命名元组的支持。

在 3.3 版更改: 添加了对 exact_type 的支持。

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

来自 token 模块的所有常量也从 tokenize 导出,以及三个附加令牌类型值:

tokenize.COMMENT

用于表示注释的令牌值。

tokenize.NL

用于表示非终止换行符的令牌值。 NEWLINE标记表示Python代码的逻辑行的结束;当逻辑代码行在多个物理线路上连续时,生成NL令牌。

tokenize.ENCODING

表示用于将源字节解码为文本的编码的令牌值。 tokenize() 返回的第一个令牌将始终是一个ENCODING令牌。

提供另一功能以反转令牌化过程。这对创建标记脚本,修改令牌流和回写修改的脚本的工具很有用。

tokenize.untokenize(iterable)

将令牌转换回Python源代码。 iterable 必须返回具有至少两个元素的序列,即令牌类型和令牌字符串。任何其他序列元素都将被忽略。

重构的脚本作为单个字符串返回。结果保证将令牌化返回以匹配输入,使得转换是无损的并且确保往返行程。保证仅适用于令牌类型和令牌字符串,因为令牌之间的间隔(列位置)可能改变。

它返回字节,使用ENCODING令牌编码,ENCODING令牌是 tokenize() 输出的第一个令牌序列。

tokenize() 需要检测它标记化的源文件的编码。它使用的功能可用:

tokenize.detect_encoding(readline)

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

它将最多调用readline一次,并返回所使用的编码(作为字符串)以及它读入的任何行(未从字节解码)的列表。

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

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

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

tokenize.open(filename)

使用 detect_encoding() 检测到的编码以只读模式打开文件。

3.2 新版功能.

exception tokenize.TokenError

在可能分割为多行的docstring或表达式未在文件中的任何位置完成时触发:

"""Beginning of
docstring

要么:

[1,
 2,
 3

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

32.7.2. 命令行用法

3.3 新版功能.

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

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

接受以下选项:

-h, --help

显示此帮助消息并退出

-e, --exact

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

如果指定了 filename.py,它的内容被标记为stdout。否则,对stdin执行标记化。

32.7.3. 例子

将浮点文本转换为十进制对象的脚本重写器示例:

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      ''