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() 方法相同的接口。每次調用這個函數都要 返回字節類型輸入的一行數據。

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. The line passed (the last tuple item) is the logical line; continuation lines are included. The 5 tuple is returned as a named tuple with the field names: type string start end line.

返回的 named tuple 有一個額外的屬性,名為 exact_type ,包含了 OP 標記的確切操作符類型。 對于所有其他標記類型, exact_type 等于命名元組的 type 字段。

在 3.1 版更改: 增加了對 named tuple 的支持。

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

根據:pep:263tokenize() 通過尋找 UTF-8 BOM 或編碼 cookie 來確定文件的源編碼。

所有來自 token 模塊的常量也可從 tokenize 導出。

提供了另一個函數來逆轉標記化過程。這對于創建對腳本進行標記、修改標記流并寫回修改后腳本的工具很有用。

tokenize.untokenize(iterable)?

將令牌轉換為 Python 源代碼。 iterable 必須返回至少有兩個元素的序列,即令牌類型和令牌字符串。任何額外的序列元素都會被忽略。

重構的腳本以單個字符串的形式返回。 結果被保證為標記回與輸入相匹配,因此轉換是無損的,并保證來回操作。 該保證只適用于標記類型和標記字符串,因為標記之間的間距(列位置)可能會改變。

It returns bytes, encoded using the ENCODING token, which is the first token sequence output by 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)