# 32.7. `tokenize` - Python源代码

源代码： Lib / tokenize.py

`tokenize`模块为Python源代码提供了一个词法扫描器，在Python中实现。该模块中的扫描器也返回作为标记的注释，使得它对于实现“漂亮打印机”很有用，包括用于屏幕显示的着色器。

为了简化令牌流处理，使用通用的`token.OP`令牌类型返回所有​​Operators和Delimiters令牌。确切类型可以通过检查`tokenize.tokenize()`返回的named tuple上的`exact_type`属性来确定。

## 32.7.1. Tokenizing Input

主要入口点是generator：

`tokenize.``tokenize`(readline)

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

  The generator produces 5-tuples with these members: the token type; the token string; a 2-tuple `(srow, scol)` of ints specifying the row and column where the token begins in the source; a 2-tuple `(erow, ecol)` of ints specifying the row and column where the token ends in the source; and the line on which the token was found. 传递的行（最后一个元组项）是逻辑行；连续线。5元组作为named tuple返回，其字段名称为：`type string start end 行`。

  返回的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. Command-Line Usage

版本3.3中的新功能。

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

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

接受以下选项：

`-h````,` `--help```

  显示此帮助消息并退出

`-e````,` `--exact```

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

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

## 32.7.3. Examples

将浮点字面值转换为十进制对象的脚本重写器示例：

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