tokenize --- 对 Python 代码使用的标记解析器¶
源码: Lib/tokenize.py
tokenize 模块为 Python 源代码提供了一个词法扫描器,用 Python 实现。该模块中的扫描器也将注释作为标记返回,这使得它对于实现“漂亮的输出器”非常有用,包括用于屏幕显示的着色器。
为了简化标记流的处理,所有的 运算符 和 定界符 以及 Ellipsis 返回时都会打上通用的 OP 标记。 可以通过 tokenize.tokenize() 返回的 named tuple 对象的 exact_type 属性来获得确切的标记类型。
对输入进行解析标记¶
主要的入口是一个 generator:
- 
tokenize.tokenize(readline)¶
- 生成器 - tokenize()需要一个 readline 参数,这个参数必须是一个可调用对象,且能提供与文件对象的- io.IOBase.readline()方法相同的接口。每次调用这个函数都要 返回字节类型输入的一行数据。- 生成器产生 5 个具有这些成员的元组:令牌类型;令牌字符串;指定令牌在源中开始的行和列的 2 元组 - (srow, scol);指定令牌在源中结束的行和列的 2 元组- (erow, ecol);以及发现令牌的行。所传递的行(最后一个元组项)是 实际的 行。 5 个元组以 named tuple 的形式返回,字段名是:- type string start end line。- 返回的 named tuple 有一个额外的属性,名为 - exact_type,包含了- OP标记的确切操作符类型。 对于所有其他标记类型,- exact_type等于命名元组的- type字段。- 在 3.1 版更改: 增加了对 named tuple 的支持。 - 在 3.3 版更改: 添加了对 - exact_type的支持。- 根据 PEP 263 , - tokenize()通过寻找 UTF-8 BOM 或编码 cookie 来确定文件的源编码。
- 
tokenize.generate_tokens(readline)¶
- 对读取 unicode 字符串而不是字节的源进行标记。 - 和 - 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 两次,并返回所使用的编码(作为一个字符串)和它所读入的任何行(不是从字节解码的)的 list 。 - 它从 UTF-8 BOM 或编码 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¶
- 当文件中任何地方没有完成 docstring 或可能被分割成几行的表达式时触发,例如: - """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() 读取 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)